From d0bbeeee526543b48e3143231fabaa0c0e10e0c4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 1 Mar 2023 14:22:53 +0900
Subject: [PATCH] :art:

---
 .../server/api/endpoints/admin/roles/users.ts |  1 +
 .../frontend/src/pages/admin/roles.role.vue   | 69 +++++++++-----
 packages/frontend/src/pages/my-lists/list.vue | 92 +++++++++----------
 3 files changed, 90 insertions(+), 72 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
index 930d3fee40..35edca5460 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
@@ -69,6 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			return await Promise.all(assigns.map(async assign => ({
 				id: assign.id,
+				createdAt: assign.createdAt,
 				user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
 				expiresAt: assign.expiresAt,
 			})));
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 7951086bf1..c5792f649c 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -30,12 +30,19 @@
 
 							<template #default="{ items }">
 								<div class="_gaps_s">
-									<div v-for="item in items" :key="item.user.id" :class="$style.userItem">
-										<MkA :class="$style.user" :to="`/user-info/${item.user.id}`">
-											<MkUserCardMini :user="item.user"/>
-										</MkA>
-										<button v-if="item.expiresAt != null" class="_button" :class="$style.expiresAt" @click="showExpireInfo(item, $event)"><i class="ti ti-clock-hour-3"></i></button>
-										<button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
+									<div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedItems.includes(item.id) }]">
+										<div :class="$style.userItemMain">
+											<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.user.id}`">
+												<MkUserCardMini :user="item.user"/>
+											</MkA>
+											<button class="_button" :class="$style.userToggle" @click="toggleItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
+											<button class="_button" :class="$style.unassign" @click="unassign(item.user, $event)"><i class="ti ti-x"></i></button>
+										</div>
+										<div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
+											<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
+											<div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div>
+											<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
+										</div>
 									</div>
 								</div>
 							</template>
@@ -77,6 +84,8 @@ const usersPagination = {
 	})),
 };
 
+let expandedItems = $ref([]);
+
 const role = reactive(await os.api('admin/roles/show', {
 	roleId: props.id,
 }));
@@ -129,7 +138,7 @@ async function assign() {
 		: null;
 
 	await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt });
-	role.users.push(user);
+	//role.users.push(user);
 }
 
 async function unassign(user, ev) {
@@ -139,16 +148,17 @@ async function unassign(user, ev) {
 		danger: true,
 		action: async () => {
 			await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id });
-			role.users = role.users.filter(u => u.id !== user.id);
+			//role.users = role.users.filter(u => u.id !== user.id);
 		},
 	}], ev.currentTarget ?? ev.target);
 }
 
-async function showExpireInfo(assignment) {
-	os.alert({
-		type: 'info',
-		text: assignment.expiresAt.toLocaleString(),
-	});
+async function toggleItem(item) {
+	if (expandedItems.includes(item.id)) {
+		expandedItems = expandedItems.filter(x => x !== item.id);
+	} else {
+		expandedItems.push(item.id);
+	}
 }
 
 const headerActions = $computed(() => []);
@@ -162,24 +172,41 @@ definePageMetadata(computed(() => ({
 </script>
 
 <style lang="scss" module>
-.userItem {
+.userItemMain {
 	display: flex;
 }
 
-.user {
-	flex: 1;
-	min-width: 0;
+.userItemSub {
+	padding: 6px 12px;
+	font-size: 85%;
+	color: var(--fgTransparentWeak);
 }
 
-.expiresAt,
+.userItemMainBody {
+	flex: 1;
+	min-width: 0;
+	margin-right: 8px;
+
+	&:hover {
+		text-decoration: none;
+	}
+}
+
+.userToggle,
 .unassign {
 	width: 32px;
 	height: 32px;
-	margin-left: 8px;
 	align-self: center;
 }
 
-.expiresAt + .unassign {
-	margin-left: 0;
+.chevron {
+	display: block;
+	transition: transform 0.1s ease-out;
+}
+
+.userItem.userItemOpend {
+	.chevron {
+		transform: rotateX(180deg);
+	}
 }
 </style>
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index f47b4bf90f..a6a3974d0c 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -2,13 +2,13 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="700">
-		<div class="mk-list-page">
+		<div>
 			<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
 				<div v-if="list" class="">
-					<div class="">
+					<div class="_buttons">
 						<MkButton inline @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
 						<MkButton inline @click="renameList()">{{ i18n.ts.rename }}</MkButton>
-						<MkButton inline @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
+						<MkButton inline danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
 					</div>
 				</div>
 			</Transition>
@@ -16,18 +16,12 @@
 			<Transition :name="$store.state.animation ? '_transition_zoom' : ''" mode="out-in">
 				<div v-if="list" class="members _margin">
 					<div class="">{{ i18n.ts.members }}</div>
-					<div class="">
-						<div class="users">
-							<div v-for="user in users" :key="user.id" class="user _panel">
-								<MkAvatar :user="user" class="avatar" indicator link preview/>
-								<div class="body">
-									<MkUserName :user="user" class="name"/>
-									<MkAcct :user="user" class="acct"/>
-								</div>
-								<div class="action">
-									<button class="_button" @click="removeUser(user)"><i class="ti ti-x"></i></button>
-								</div>
-							</div>
+					<div class="_gaps_s">
+						<div v-for="user in users" :key="user.id" :class="$style.userItem">
+							<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+								<MkUserCardMini :user="user"/>
+							</MkA>
+							<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
 						</div>
 					</div>
 				</div>
@@ -44,6 +38,8 @@ import * as os from '@/os';
 import { mainRouter } from '@/router';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
+import { userPage } from '@/filters/user';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
 
 const props = defineProps<{
 	listId: string;
@@ -76,13 +72,20 @@ function addUser() {
 	});
 }
 
-function removeUser(user) {
-	os.api('users/lists/pull', {
-		listId: list.id,
-		userId: user.id,
-	}).then(() => {
-		users = users.filter(x => x.id !== user.id);
-	});
+async function removeUser(user, ev) {
+	os.popupMenu([{
+		text: i18n.ts.remove,
+		icon: 'ti ti-x',
+		danger: true,
+		action: async () => {
+			os.api('users/lists/pull', {
+				listId: list.id,
+				userId: user.id,
+			}).then(() => {
+				users = users.filter(x => x.id !== user.id);
+			});
+		},
+	}], ev.currentTarget ?? ev.target);
 }
 
 async function renameList() {
@@ -126,37 +129,24 @@ definePageMetadata(computed(() => list ? {
 } : null));
 </script>
 
-<style lang="scss" scoped>
-.mk-list-page {
-	> .members {
-		> ._content {
-			> .users {
-				> .user {
-					display: flex;
-					align-items: center;
-					padding: 16px;
+<style lang="scss" module>
+.userItem {
+	display: flex;
+}
 
-					> .avatar {
-						width: 50px;
-						height: 50px;
-					}
+.userItemBody {
+	flex: 1;
+	min-width: 0;
+	margin-right: 8px;
 
-					> .body {
-						flex: 1;
-						padding: 8px;
-
-						> .name {
-							display: block;
-							font-weight: bold;
-						}
-
-						> .acct {
-							opacity: 0.5;
-						}
-					}
-				}
-			}
-		}
+	&:hover {
+		text-decoration: none;
 	}
 }
+
+.remove {
+	width: 32px;
+	height: 32px;
+	align-self: center;
+}
 </style>