refactor: remove badges and subtitle implementation from table and used slots instead
This commit is contained in:
@@ -23,11 +23,9 @@ declare module '@vue/runtime-core' {
|
|||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
||||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
|
||||||
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
|
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
|
||||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||||
|
|||||||
@@ -27,41 +27,42 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HoppSmartTable
|
<div v-else class="m-5">
|
||||||
v-else
|
<HoppSmartTable
|
||||||
:list="newTeamsList"
|
:list="newTeamsList"
|
||||||
:headings="headings"
|
:headings="headings"
|
||||||
@goToDetails="goToTeamDetails"
|
@goToDetails="goToTeamDetails"
|
||||||
cell-styles="px-6 py-3"
|
cell-styles="px-6 py-3"
|
||||||
>
|
>
|
||||||
<template #action="{ item }">
|
<template #action="{ item }">
|
||||||
<td>
|
<td>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<tippy interactive trigger="click" theme="popover">
|
<tippy interactive trigger="click" theme="popover">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconMoreHorizontal"
|
:icon="IconMoreHorizontal"
|
||||||
/>
|
/>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
<div
|
<div
|
||||||
ref="tippyActions"
|
ref="tippyActions"
|
||||||
class="flex flex-col focus:outline-none"
|
class="flex flex-col focus:outline-none"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@keyup.escape="hide()"
|
@keyup.escape="hide()"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
:label="t('teams.delete_team')"
|
:label="t('teams.delete_team')"
|
||||||
class="!hover:bg-red-600 w-full"
|
class="!hover:bg-red-600 w-full"
|
||||||
@click="deleteTeam(item)"
|
@click="deleteTeam(item)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartTable>
|
</HoppSmartTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="hasNextPage && teamsList.length >= teamsPerPage"
|
v-if="hasNextPage && teamsList.length >= teamsPerPage"
|
||||||
|
|||||||
@@ -31,62 +31,105 @@
|
|||||||
|
|
||||||
<div v-else-if="error">{{ t('users.load_list_error') }}</div>
|
<div v-else-if="error">{{ t('users.load_list_error') }}</div>
|
||||||
|
|
||||||
<HoppSmartTable
|
<div v-else-if="usersList.length >= 1" class="m-5">
|
||||||
v-else-if="usersList.length >= 1"
|
<HoppSmartTable
|
||||||
cell-styles="px-6 py-2"
|
cell-styles="px-6 py-1"
|
||||||
:list="newUsersList"
|
:list="newUsersList"
|
||||||
:headings="headings"
|
:headings="headings"
|
||||||
@goToDetails="goToUserDetails"
|
:modify-col-names="colNames"
|
||||||
:badge-name="t('users.admin')"
|
@goToDetails="goToUserDetails"
|
||||||
:badge-row-indices="adminUsersIndices"
|
>
|
||||||
badge-col-name="name"
|
<template #name="{ item }">
|
||||||
:subtitles="subtitles"
|
<div>
|
||||||
>
|
<div class="flex flex-col truncate justify-center">
|
||||||
<template #action="{ item }">
|
<span
|
||||||
<td>
|
v-if="item.name.length"
|
||||||
<div class="relative">
|
class="my-1 truncate whitespace-normal"
|
||||||
<span>
|
>
|
||||||
<tippy interactive trigger="click" theme="popover">
|
{{ item.name }}
|
||||||
<HoppButtonSecondary
|
</span>
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:icon="IconMoreHorizontal"
|
<span
|
||||||
/>
|
v-if="item.name.length === 0 && !isUserAdmin(item)"
|
||||||
<template #content="{ hide }">
|
class="mx-auto justify-center"
|
||||||
<div
|
>-</span
|
||||||
ref="tippyActions"
|
>
|
||||||
class="flex flex-col focus:outline-none"
|
|
||||||
tabindex="0"
|
<span
|
||||||
@keyup.escape="hide()"
|
v-if="isUserAdmin(item)"
|
||||||
>
|
class="text-xs font-medium px-3 py-1 my-1 rounded-full bg-green-900 text-green-300 w-min"
|
||||||
<HoppSmartItem
|
>
|
||||||
v-if="!isUserAdmin(item)"
|
Admin
|
||||||
:icon="IconUserCheck"
|
</span>
|
||||||
:label="t('users.make_admin')"
|
</div>
|
||||||
class="!hover:bg-emerald-600"
|
|
||||||
@click="makeUserAdmin(item)"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
v-else
|
|
||||||
:icon="IconUserMinus"
|
|
||||||
:label="t('users.remove_admin_status')"
|
|
||||||
class="!hover:bg-emerald-600"
|
|
||||||
@click="makeAdminToUser(item)"
|
|
||||||
/>
|
|
||||||
<HoppSmartItem
|
|
||||||
v-if="!isUserAdmin(item)"
|
|
||||||
:icon="IconTrash"
|
|
||||||
:label="t('users.delete_user')"
|
|
||||||
class="!hover:bg-red-600"
|
|
||||||
@click="deleteUser(item)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</tippy>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</template>
|
||||||
</template>
|
|
||||||
</HoppSmartTable>
|
<template #createdOn="{ item }">
|
||||||
|
<div class="flex flex-col truncate">
|
||||||
|
<span v-if="item" class="truncate">
|
||||||
|
{{ getCreatedDate(item.createdOn) }}
|
||||||
|
</span>
|
||||||
|
<span v-else> - </span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-400 text-tiny">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
{{ getCreatedTime(item.createdOn) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #action="{ item }">
|
||||||
|
<td>
|
||||||
|
<div class="relative">
|
||||||
|
<span>
|
||||||
|
<tippy interactive trigger="click" theme="popover">
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconMoreHorizontal"
|
||||||
|
/>
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
tabindex="0"
|
||||||
|
@keyup.escape="hide()"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-if="!isUserAdmin(item)"
|
||||||
|
:icon="IconUserCheck"
|
||||||
|
:label="t('users.make_admin')"
|
||||||
|
class="!hover:bg-emerald-600"
|
||||||
|
@click="makeUserAdmin(item)"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-else
|
||||||
|
:icon="IconUserMinus"
|
||||||
|
:label="t('users.remove_admin_status')"
|
||||||
|
class="!hover:bg-emerald-600"
|
||||||
|
@click="makeAdminToUser(item)"
|
||||||
|
/>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-if="!isUserAdmin(item)"
|
||||||
|
:icon="IconTrash"
|
||||||
|
:label="t('users.delete_user')"
|
||||||
|
class="!hover:bg-red-600"
|
||||||
|
@click="deleteUser(item)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
</HoppSmartTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else class="flex justify-center">{{ t('users.no_users') }}</div>
|
<div v-else class="flex justify-center">{{ t('users.no_users') }}</div>
|
||||||
|
|
||||||
@@ -181,7 +224,7 @@ const newUsersList = computed(() => {
|
|||||||
uid: user.uid,
|
uid: user.uid,
|
||||||
name: user.displayName ?? '',
|
name: user.displayName ?? '',
|
||||||
email: user.email ?? '',
|
email: user.email ?? '',
|
||||||
createdOn: getCreatedDate(user.createdOn),
|
createdOn: user.createdOn,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -194,19 +237,6 @@ const isUserAdmin = (
|
|||||||
})[0].isAdmin;
|
})[0].isAdmin;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns index of all the admin users
|
|
||||||
const adminUsersIndices = computed(() => {
|
|
||||||
const indices = [];
|
|
||||||
for (let index = 0; index < usersList.value.length; index++)
|
|
||||||
if (usersList.value[index].isAdmin) indices.push(index);
|
|
||||||
return indices;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Returns created time of all the users
|
|
||||||
const createdTime = computed(() =>
|
|
||||||
usersList.value.map((user) => getCreatedTime(user.createdOn))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Headers that are used in the table
|
// Headers that are used in the table
|
||||||
const headings = [
|
const headings = [
|
||||||
t('users.id'),
|
t('users.id'),
|
||||||
@@ -216,13 +246,8 @@ const headings = [
|
|||||||
'',
|
'',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Subtitles that are used in the table
|
//Names of the columns to be modified by this component
|
||||||
const subtitles = reactive([
|
const colNames = ['name', 'createdOn'];
|
||||||
{
|
|
||||||
colName: 'createdOn',
|
|
||||||
subtitle: createdTime,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Send Invitation through Email
|
// Send Invitation through Email
|
||||||
const sendInvitation = useMutation(InviteNewUserDocument);
|
const sendInvitation = useMutation(InviteNewUserDocument);
|
||||||
|
|||||||
@@ -24,13 +24,33 @@
|
|||||||
<p class="text-xl">{{ t('users.no_invite') }}</p>
|
<p class="text-xl">{{ t('users.no_invite') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HoppSmartTable
|
<div v-else class="m-5">
|
||||||
v-else
|
<HoppSmartTable
|
||||||
cell-styles="px-6 py-2"
|
cell-styles="px-6 py-1"
|
||||||
:list="newInvitedUsersList"
|
:list="newInvitedUsersList"
|
||||||
:headings="headings"
|
:headings="headings"
|
||||||
:subtitles="subtitles"
|
:modify-col-names="colNames"
|
||||||
/>
|
>
|
||||||
|
<template #invitedOn="{ item }">
|
||||||
|
<div class="flex flex-col truncate">
|
||||||
|
<span v-if="item" class="truncate">
|
||||||
|
{{ getCreatedDate(item.invitedOn) }}
|
||||||
|
</span>
|
||||||
|
<span v-else> - </span>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="text-gray-400 text-tiny">
|
||||||
|
<span>
|
||||||
|
<span>
|
||||||
|
{{ getCreatedTime(item.invitedOn) }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</HoppSmartTable>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +58,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useQuery } from '@urql/vue';
|
import { useQuery } from '@urql/vue';
|
||||||
import { InvitedUsersDocument } from '../../helpers/backend/graphql';
|
import { InvitedUsersDocument } from '../../helpers/backend/graphql';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@@ -65,18 +85,11 @@ const newInvitedUsersList = computed(() => {
|
|||||||
adminUid: user.adminUid,
|
adminUid: user.adminUid,
|
||||||
adminEmail: user.adminEmail,
|
adminEmail: user.adminEmail,
|
||||||
inviteeEmail: user.inviteeEmail,
|
inviteeEmail: user.inviteeEmail,
|
||||||
invitedOn: getCreatedDate(user.invitedOn),
|
invitedOn: user.invitedOn,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Returns the created time of all the invited user
|
|
||||||
const createdTime = computed(() => {
|
|
||||||
return invitedUsers.value?.map((user) => {
|
|
||||||
return getCreatedTime(user.invitedOn);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Headings used in the table
|
// Headings used in the table
|
||||||
const headings = [
|
const headings = [
|
||||||
t('users.admin_id'),
|
t('users.admin_id'),
|
||||||
@@ -85,11 +98,5 @@ const headings = [
|
|||||||
t('users.invited_on'),
|
t('users.invited_on'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Subtitles used in the table
|
const colNames = ['invitedOn'];
|
||||||
const subtitles = reactive([
|
|
||||||
{
|
|
||||||
colName: 'invitedOn',
|
|
||||||
subtitle: createdTime,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-auto rounded-md border border-dividerDark shadow-md m-5">
|
<div class="overflow-auto rounded-md border border-dividerDark shadow-md">
|
||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead class="bg-primaryLight">
|
<thead class="bg-primaryLight">
|
||||||
<tr
|
<tr
|
||||||
@@ -25,57 +25,18 @@
|
|||||||
class="max-w-40"
|
class="max-w-40"
|
||||||
:class="cellStyles"
|
:class="cellStyles"
|
||||||
>
|
>
|
||||||
|
<!-- Custom implementation of the particular column -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<!-- Column with Badge -->
|
<div v-if="modifyColNames?.includes(colIndex.toString())">
|
||||||
<div
|
<slot :name="colIndex.toString()" :item="item"></slot>
|
||||||
v-if="
|
|
||||||
colIndex.toString() === badgeColName &&
|
|
||||||
badgeRowIndices?.includes(rowIndex)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="flex flex-col truncate">
|
|
||||||
<span v-if="data" class="mt-1 truncate whitespace-normal">
|
|
||||||
{{ data }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
class="text-xs font-medium px-3 py-1 my-1 rounded-full bg-green-900 text-green-300 w-min"
|
|
||||||
>
|
|
||||||
{{ badgeName }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Column with Subtitles -->
|
<!-- Generic implementation of the column -->
|
||||||
<div
|
<div v-else class="flex flex-col truncate text-center">
|
||||||
v-else-if="subtitleColumns.includes(colIndex.toString())"
|
|
||||||
class="flex flex-col truncate"
|
|
||||||
>
|
|
||||||
<span v-if="data" class="truncate">
|
<span v-if="data" class="truncate">
|
||||||
{{ data }}
|
{{ data }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else> - </span>
|
<span v-else class=""> - </span>
|
||||||
|
|
||||||
<div v-for="item in subtitles">
|
|
||||||
<div
|
|
||||||
v-if="item.colName === colIndex.toString()"
|
|
||||||
class="text-gray-400 text-tiny"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<span>
|
|
||||||
{{ itemSubtitle(item.subtitle, rowIndex) }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Column with no subtitle or badge -->
|
|
||||||
<div v-else class="flex flex-col truncate">
|
|
||||||
<span v-if="data" class="truncate">
|
|
||||||
{{ data }}
|
|
||||||
</span>
|
|
||||||
<span v-else> - </span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -88,9 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup generic="Item extends Record<string, unknown>">
|
<script lang="ts" setup generic="Item extends Record<string, unknown>">
|
||||||
import { computed } from "vue"
|
defineProps<{
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
/** Whether to show the vertical border between columns */
|
/** Whether to show the vertical border between columns */
|
||||||
yBorder?: boolean
|
yBorder?: boolean
|
||||||
/** The list of items to be displayed in the table */
|
/** The list of items to be displayed in the table */
|
||||||
@@ -99,31 +58,11 @@ const props = defineProps<{
|
|||||||
headings: string[]
|
headings: string[]
|
||||||
/** The styles to be applied to the table cells */
|
/** The styles to be applied to the table cells */
|
||||||
cellStyles?: string
|
cellStyles?: string
|
||||||
/** The name of the badge */
|
/** The names of the columns which have to modified using slots */
|
||||||
badgeName?: string
|
modifyColNames?: string[]
|
||||||
/** The indices of the rows that needs to have a badge */
|
|
||||||
badgeRowIndices?: (number | undefined)[]
|
|
||||||
/** The index of the column that needs to have a badge */
|
|
||||||
badgeColName?: string
|
|
||||||
/** The subtitles to be displayed for the columns */
|
|
||||||
subtitles?: Array<{
|
|
||||||
/** The name of the column that needs to have a subtitle */
|
|
||||||
colName: string
|
|
||||||
/** The subtitle to be displayed for the column */
|
|
||||||
subtitle: string | string[]
|
|
||||||
}>
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(event: "goToDetails", item: Item): void
|
(event: "goToDetails", item: Item): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Returns all the columns that needs to have a subtitle
|
|
||||||
const subtitleColumns = computed(() =>
|
|
||||||
props.subtitles ? props.subtitles.map((item) => item.colName) : []
|
|
||||||
)
|
|
||||||
|
|
||||||
// Returns the subtitle for the given column and row
|
|
||||||
const itemSubtitle = (subtitle: string | string[], rowIndex: number) =>
|
|
||||||
typeof subtitle === "object" ? subtitle[rowIndex] : subtitle
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user