index.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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 } 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. ["moderator", "Moderator"],
  155. ["user", "User"]
  156. ]
  157. },
  158. {
  159. name: "emailAddress",
  160. displayName: "Email Address",
  161. property: "email.address",
  162. filterTypes: ["contains", "exact", "regex"],
  163. defaultFilterType: "contains"
  164. },
  165. {
  166. name: "emailVerified",
  167. displayName: "Email Verified",
  168. property: "email.verified",
  169. filterTypes: ["boolean"],
  170. defaultFilterType: "boolean"
  171. },
  172. {
  173. name: "songsRequested",
  174. displayName: "Songs Requested",
  175. property: "statistics.songsRequested",
  176. filterTypes: [
  177. "numberLesserEqual",
  178. "numberLesser",
  179. "numberGreater",
  180. "numberGreaterEqual",
  181. "numberEquals"
  182. ],
  183. defaultFilterType: "numberLesser"
  184. }
  185. ]);
  186. const { openModal } = useModalsStore();
  187. const { hasPermission } = useUserAuthStore();
  188. const edit = userId => {
  189. openModal({ modal: "editUser", props: { userId } });
  190. };
  191. onMounted(() => {
  192. if (route.query.userId) edit(route.query.userId);
  193. });
  194. </script>
  195. <template>
  196. <div class="admin-tab container">
  197. <page-metadata title="Admin | Users" />
  198. <div class="card tab-info">
  199. <div class="info-row">
  200. <h1>Users</h1>
  201. <p>Manage users</p>
  202. </div>
  203. </div>
  204. <advanced-table
  205. :column-default="columnDefault"
  206. :columns="columns"
  207. :filters="filters"
  208. model="users"
  209. name="admin-users"
  210. :max-width="1200"
  211. >
  212. <template #column-options="slotProps">
  213. <div class="row-options">
  214. <button
  215. v-if="hasPermission('users.update')"
  216. class="button is-primary icon-with-button material-icons"
  217. @click="edit(slotProps.item._id)"
  218. :disabled="slotProps.item.removed"
  219. content="Edit User"
  220. v-tippy
  221. >
  222. edit
  223. </button>
  224. <router-link
  225. :to="{ path: `/u/${slotProps.item.username}` }"
  226. target="_blank"
  227. class="button is-primary icon-with-button material-icons"
  228. :disabled="slotProps.item.removed"
  229. content="View Profile"
  230. v-tippy
  231. >
  232. person
  233. </router-link>
  234. </div>
  235. </template>
  236. <template #column-profilePicture="slotProps">
  237. <profile-picture
  238. :avatar="slotProps.item.avatar"
  239. :name="
  240. slotProps.item.name
  241. ? slotProps.item.name
  242. : slotProps.item.username
  243. "
  244. />
  245. </template>
  246. <template #column-name="slotProps">
  247. <span :title="slotProps.item.name">{{
  248. slotProps.item.name
  249. }}</span>
  250. </template>
  251. <template #column-username="slotProps">
  252. <span :title="slotProps.item.username">{{
  253. slotProps.item.username
  254. }}</span>
  255. </template>
  256. <template #column-_id="slotProps">
  257. <span :title="slotProps.item._id">{{
  258. slotProps.item._id
  259. }}</span>
  260. </template>
  261. <template #column-githubId="slotProps">
  262. <span
  263. v-if="slotProps.item.services.github"
  264. :title="slotProps.item.services.github.id"
  265. >{{ slotProps.item.services.github.id }}</span
  266. >
  267. </template>
  268. <template #column-hasPassword="slotProps">
  269. <span :title="slotProps.item.hasPassword">{{
  270. slotProps.item.hasPassword
  271. }}</span>
  272. </template>
  273. <template #column-role="slotProps">
  274. <span :title="slotProps.item.role">{{
  275. slotProps.item.role
  276. }}</span>
  277. </template>
  278. <template #column-emailAddress="slotProps">
  279. <span :title="slotProps.item.email.address">{{
  280. slotProps.item.email.address
  281. }}</span>
  282. </template>
  283. <template #column-emailVerified="slotProps">
  284. <span :title="slotProps.item.email.verified">{{
  285. slotProps.item.email.verified
  286. }}</span>
  287. </template>
  288. <template #column-songsRequested="slotProps">
  289. <span :title="slotProps.item.statistics.songsRequested">{{
  290. slotProps.item.statistics.songsRequested
  291. }}</span>
  292. </template>
  293. </advanced-table>
  294. </div>
  295. </template>
  296. <style lang="less" scoped>
  297. .profile-picture {
  298. max-width: 50px !important;
  299. max-height: 50px !important;
  300. }
  301. :deep(.profile-picture.using-initials span) {
  302. font-size: 20px !important; // 2/5th of .profile-picture height/width
  303. }
  304. </style>