浏览代码

UI Unification: Standardize ReviewsSection and cleanup Admin Dashboard (minimalist tabs, removed icons)

unknown 1 天之前
父节点
当前提交
755322f422
共有 2 个文件被更改,包括 105 次插入122 次删除
  1. 94 111
      src/components/admin/ReviewsSection.vue
  2. 11 11
      src/pages/Admin.vue

+ 94 - 111
src/components/admin/ReviewsSection.vue

@@ -1,117 +1,105 @@
 <template>
   <div class="space-y-6">
-    <div class="flex items-center justify-between">
-      <h2 class="text-2xl font-display font-bold">{{ t('admin.reviews.title') }}</h2>
-      <div class="text-sm text-muted-foreground">
-        {{ total }} {{ t('admin.reviews.total') }}
+    <!-- Header / Summary Bar -->
+    <div class="flex items-center justify-between bg-card/40 p-4 rounded-2xl border border-border/50">
+      <h2 class="text-sm font-bold uppercase tracking-widest text-primary ml-2">{{ t('admin.reviews.title') }}</h2>
+      <div class="text-xs font-bold text-muted-foreground uppercase tracking-widest bg-muted/30 px-4 py-2 rounded-xl border border-border/50">
+        {{ t("admin.total") }}: {{ total }}
       </div>
     </div>
 
-    <!-- Reviews List -->
-    <div class="grid gap-4">
-      <div v-if="loading" class="flex justify-center py-12">
-        <Loader2 class="w-8 h-8 animate-spin text-primary" />
-      </div>
-      
-      <div v-else-if="reviews.length === 0" class="text-center py-12 bg-card/50 rounded-3xl border border-dashed border-border">
-        <MessageSquare class="w-12 h-12 text-muted-foreground/30 mx-auto mb-4" />
-        <p class="text-muted-foreground">{{ t('admin.reviews.noReviews') }}</p>
-      </div>
-
-      <div 
-        v-for="review in reviews" 
-        :key="review.id"
-        class="bg-card/50 backdrop-blur-xl border border-border/50 p-6 rounded-3xl hover:border-primary/30 transition-all group relative overflow-hidden"
-      >
-        <!-- Status Badge -->
-        <div class="absolute top-6 right-6 flex items-center gap-2">
-          <span 
-            :class="[
-              'px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider',
-              review.review_approved ? 'bg-emerald-500/10 text-emerald-500' : 'bg-amber-500/10 text-amber-500'
-            ]"
-          >
-            {{ review.review_approved ? t('admin.reviews.approved') : t('admin.reviews.pending') }}
-          </span>
-        </div>
-
-        <div class="flex items-start gap-4">
-          <div class="w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center text-primary font-bold text-lg">
-            {{ review.first_name[0] }}
-          </div>
-          <div class="flex-1 min-w-0">
-            <div class="flex items-center gap-2 mb-1">
-              <p class="font-bold">{{ review.first_name }} {{ review.last_name }}</p>
-              <span class="text-muted-foreground text-xs">•</span>
-              <p class="text-xs text-muted-foreground">{{ new Date(review.created_at).toLocaleDateString() }}</p>
-            </div>
-            <p class="text-xs text-muted-foreground mb-4 truncate">{{ review.email }}</p>
-
-            <div class="flex gap-0.5 mb-4">
-              <Star 
-                v-for="i in 5" 
-                :key="i"
-                class="w-4 h-4" 
-                :class="i <= review.rating ? 'text-yellow-500 fill-yellow-500' : 'text-muted-foreground/20'"
-              />
-            </div>
+    <!-- Empty State -->
+    <div v-if="!loading && reviews.length === 0" class="flex flex-col items-center justify-center py-20 bg-card/40 border border-border/50 rounded-3xl opacity-50">
+      <MessageSquare class="w-12 h-12 text-muted-foreground/20 mb-4" />
+      <p class="text-sm text-muted-foreground">{{ t('admin.reviews.noReviews') }}</p>
+    </div>
 
-            <p class="text-foreground/80 italic bg-background/50 p-4 rounded-2xl border border-border/50 mb-6 font-serif">
-              "{{ review.review_text }}"
-            </p>
+    <!-- Loading State -->
+    <div v-if="loading" class="flex items-center justify-center py-20">
+      <Loader2 class="w-8 h-8 text-primary animate-spin" />
+    </div>
 
-            <div class="flex items-center gap-3">
-              <Button 
-                v-if="!review.review_approved"
-                variant="default" 
-                size="sm"
-                class="rounded-xl h-9 px-4"
-                @click="handleApprove(review.id)"
-              >
-                <Check class="w-4 h-4 mr-2" />
-                {{ t('admin.reviews.approve') }}
-              </Button>
-              <Button 
-                variant="outline" 
-                size="sm"
-                class="rounded-xl h-9 px-4 border-rose-500/20 text-rose-500 hover:bg-rose-500/10 hover:border-rose-500/30"
-                @click="handleDelete(review.id)"
-              >
-                <Trash2 class="w-4 h-4 mr-2" />
-                {{ t('admin.reviews.delete') }}
-              </Button>
-              
-              <div class="flex-1" />
-              
-              <p class="text-[10px] text-muted-foreground uppercase tracking-widest font-medium">
-                Order #{{ review.id }}
-              </p>
-            </div>
-          </div>
-        </div>
+    <!-- Table Layout -->
+    <div v-if="!loading && reviews.length > 0" class="bg-card/40 backdrop-blur-md border border-border/50 rounded-3xl overflow-hidden shadow-xl">
+      <div class="overflow-x-auto">
+        <table class="w-full text-left border-collapse">
+          <thead>
+            <tr class="bg-muted/30 border-b border-border/50">
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider">{{ t("admin.labels.user") }}</th>
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider">{{ t("admin.reviews.rating") }}</th>
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider">{{ t("admin.reviews.content") }}</th>
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider">{{ t("admin.reviews.status") }}</th>
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider">{{ t("admin.labels.registered") }}</th>
+              <th class="p-4 text-[10px] font-bold uppercase tracking-wider text-right">{{ t("admin.labels.actions") }}</th>
+            </tr>
+          </thead>
+          <tbody class="divide-y divide-border/30">
+            <tr v-for="review in reviews" :key="review.id" class="hover:bg-primary/5 transition-colors group/row">
+              <td class="p-4">
+                <div class="flex flex-col">
+                  <span class="text-sm font-bold">{{ review.first_name }} {{ review.last_name }}</span>
+                  <span class="text-[10px] text-muted-foreground opacity-50">{{ review.email }}</span>
+                </div>
+              </td>
+              <td class="p-4">
+                <div class="flex items-center gap-1">
+                  <span class="text-sm font-bold">{{ review.rating }}</span>
+                  <span class="text-[10px] text-muted-foreground">/ 5</span>
+                </div>
+              </td>
+              <td class="p-4">
+                <p class="text-xs text-foreground/80 max-w-md line-clamp-2 italic">
+                  "{{ review.review_text }}"
+                </p>
+              </td>
+              <td class="p-4">
+                <span 
+                  :class="[
+                    'px-2 py-0.5 rounded-full text-[9px] font-bold uppercase border',
+                    review.review_approved ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' : 'bg-amber-500/10 text-amber-500 border-amber-500/20'
+                  ]"
+                >
+                  {{ review.review_approved ? t('admin.reviews.approved') : t('admin.reviews.pending') }}
+                </span>
+              </td>
+              <td class="p-4 text-[10px] text-muted-foreground font-bold uppercase">
+                {{ new Date(review.created_at).toLocaleDateString() }}
+              </td>
+              <td class="p-4 text-right">
+                <div class="flex items-center justify-end gap-2">
+                  <Button 
+                    v-if="!review.review_approved"
+                    variant="outline" 
+                    size="sm"
+                    class="h-7 px-3 text-[10px] font-bold uppercase rounded-lg border-emerald-500/30 text-emerald-500 hover:bg-emerald-500/10"
+                    @click="handleApprove(review.id)"
+                  >
+                    {{ t('admin.reviews.approve') }}
+                  </Button>
+                  <Button 
+                    variant="outline" 
+                    size="sm"
+                    class="h-7 px-3 text-[10px] font-bold uppercase rounded-lg border-rose-500/30 text-rose-500 hover:bg-rose-500/10"
+                    @click="handleDelete(review.id)"
+                  >
+                    {{ t('admin.reviews.delete') }}
+                  </Button>
+                </div>
+              </td>
+            </tr>
+          </tbody>
+        </table>
       </div>
     </div>
-    
-    <!-- Paging -->
-    <div v-if="total > size" class="flex justify-center gap-2 pt-6">
-      <Button 
-        variant="outline" 
-        :disabled="page === 1"
-        @click="page--"
-        class="rounded-xl"
-      >
-        <ChevronLeft class="w-4 h-4 mr-2" />
-        {{ t('admin.common.prev') }}
-      </Button>
-      <Button 
-        variant="outline" 
-        :disabled="page * size >= total"
-        @click="page++"
-        class="rounded-xl"
-      >
-        {{ t('admin.common.next') }}
-        <ChevronRight class="w-4 h-4 ml-2" />
-      </Button>
+
+    <!-- Pagination -->
+    <div v-if="total > size" class="flex items-center justify-center gap-2 py-4">
+       <Button v-for="p in Math.ceil(total / size)" :key="p" 
+         variant="outline"
+         @click="page = p" 
+         :class="['w-8 h-8 rounded-lg font-bold text-xs transition-all p-0', page === p ? 'bg-primary text-white shadow-glow border-primary' : 'bg-card border border-border/50 text-muted-foreground hover:border-primary/50']">
+         {{ p }}
+       </Button>
     </div>
   </div>
 </template>
@@ -119,10 +107,7 @@
 <script setup lang="ts">
 import { ref, onMounted, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import { 
-  MessageSquare, Star, Check, Trash2, 
-  Loader2, ChevronLeft, ChevronRight 
-} from 'lucide-vue-next';
+import { MessageSquare, Loader2 } from 'lucide-vue-next';
 import { adminGetReviews, approveOrderReview, adminUpdateOrder } from '@/lib/api';
 import Button from '@/components/ui/button.vue';
 import { toast } from 'vue-sonner';
@@ -161,7 +146,6 @@ const handleApprove = async (orderId: number) => {
 const handleDelete = async (orderId: number) => {
   if (!confirm(t('admin.reviews.confirmDelete'))) return;
   try {
-    // We just clear the review fields for this order
     await adminUpdateOrder(orderId, {
       review_text: "",
       rating: 0,
@@ -175,6 +159,5 @@ const handleDelete = async (orderId: number) => {
 };
 
 watch(page, fetchReviews);
-
 onMounted(fetchReviews);
 </script>

+ 11 - 11
src/pages/Admin.vue

@@ -16,10 +16,10 @@
         </div>
         <div class="flex bg-card/40 backdrop-blur-md border border-border/50 rounded-2xl p-1 gap-1">
           <button v-for="tab in tabs" :key="tab.id" @click="activeTab = tab.id" :class="[
-            'flex items-center gap-2 px-6 py-2.5 rounded-xl text-sm font-bold transition-all',
+            'px-6 py-2.5 rounded-xl text-sm font-bold transition-all whitespace-nowrap',
             activeTab === tab.id ? 'bg-primary text-primary-foreground shadow-glow' : 'text-muted-foreground hover:bg-white/5 hover:text-foreground'
           ]">
-            <component :is="tab.icon" class="w-4 h-4" />{{ t('admin.tabs.' + tab.id) }}
+            {{ t('admin.tabs.' + tab.id) }}
           </button>
         </div>
       </div>
@@ -276,15 +276,15 @@ const userPage = ref(1);
 const notifyStatusMap = ref<Record<number, boolean>>({});
 const fiscalFormMap = ref<Record<number, { fiscal_qr_url: string; ikof: string; jikr: string }>>({});
 
-const tabs: { id: Tab; icon: any }[] = [
-  { id: "orders",    icon: Package },
-  { id: "materials", icon: Layers },
-  { id: "services",  icon: Database },
-  { id: "portfolio", icon: ImageIcon },
-  { id: "reviews",   icon: MessageSquare },
-  { id: "users",     icon: Users },
-  { id: "posts",     icon: Newspaper },
-  { id: "audit",     icon: History },
+const tabs: { id: Tab }[] = [
+  { id: "orders" },
+  { id: "materials" },
+  { id: "services" },
+  { id: "portfolio" },
+  { id: "reviews" },
+  { id: "users" },
+  { id: "posts" },
+  { id: "audit" },
 ];
 
 type Tab = "orders" | "materials" | "services" | "posts" | "users" | "portfolio" | "audit" | "reviews";