refactor: remove badges and subtitle implementation from table and used slots instead

This commit is contained in:
Joel Jacob Stephen
2023-07-20 17:07:26 +05:30
parent f48083ca7e
commit 24f32d79b3
5 changed files with 177 additions and 207 deletions

View File

@@ -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']

View File

@@ -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"

View File

@@ -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);

View File

@@ -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>

View File

@@ -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>