From a36e6c77f82c49e2bebd0c03838a6a0b3352a15d Mon Sep 17 00:00:00 2001 From: ngthanhvu Date: Wed, 5 Aug 2026 05:19:34 -0400 Subject: [PATCH] feat: implement cache busting mechanism and UI improvements - Added `useCacheBust` composable to manage cache invalidation after movie edits in the admin panel. - Integrated cache busting in movie sync and delete actions to ensure data consistency across pages. - Enhanced UI components in the admin movie management and member management pages for better readability and usability. - Introduced a suggestions tab in the movie detail page to recommend similar movies based on categories. - Updated styles for various elements to improve the overall aesthetic and user experience. --- AGENTS.md | 0 app/app.vue | 2 +- app/assets/css/main.css | 98 ++++ app/components/AdminToggle.vue | 6 +- app/composables/useCacheBust.ts | 37 ++ app/layouts/admin.vue | 51 +- app/pages/admin/cai-dat.vue | 103 ++-- app/pages/admin/index.vue | 392 ++++++++-------- app/pages/admin/login.vue | 107 +++-- app/pages/admin/phim/[id].vue | 800 +++++++++++++++++--------------- app/pages/admin/phim/index.vue | 264 +++++------ app/pages/admin/thanh-vien.vue | 88 ++-- app/pages/phim/[slug].vue | 42 +- 13 files changed, 1117 insertions(+), 873 deletions(-) create mode 100644 AGENTS.md create mode 100644 app/composables/useCacheBust.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e69de29 diff --git a/app/app.vue b/app/app.vue index 7b875df..d199e65 100644 --- a/app/app.vue +++ b/app/app.vue @@ -7,7 +7,7 @@ const isAdmin = computed(() => route.path.startsWith('/admin')) - + diff --git a/app/assets/css/main.css b/app/assets/css/main.css index b2faf1a..28420a9 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -76,3 +76,101 @@ select:disabled { .no-scrollbar::-webkit-scrollbar { display: none; } + +/* Admin design system — Modern Minimal Enterprise */ +@layer utilities { + .admin-page { + @apply min-h-screen bg-[#0A0B0E] text-zinc-100 antialiased; + } + + /* Flat, neutral enterprise card */ + .admin-card { + @apply rounded-2xl border border-white/[0.07] bg-[#131418] transition-colors duration-200 hover:border-white/[0.14]; + } + + /* Card with subtle top highlight for hierarchy */ + .admin-card-gradient { + @apply rounded-2xl border border-white/[0.07] bg-[#131418] transition-colors duration-200 hover:border-white/[0.14]; + background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.025), transparent 40%); + } + + .admin-input { + @apply h-11 w-full rounded-xl border border-white/[0.08] bg-white/[0.03] px-4 text-sm text-white placeholder:text-zinc-500 outline-none transition focus:border-yellow-400/40 focus:bg-white/[0.05] focus:ring-2 focus:ring-yellow-400/15; + } + + .admin-btn { + @apply inline-flex h-10 items-center justify-center gap-2 rounded-xl px-5 text-sm font-semibold transition-all duration-150 active:scale-[0.98]; + } + + .admin-btn-primary { + @apply inline-flex h-10 items-center justify-center gap-2 rounded-xl px-5 text-sm font-semibold transition-all duration-150 active:scale-[0.98] bg-yellow-400 text-zinc-950 hover:bg-yellow-300 hover:shadow-lg hover:shadow-yellow-400/20; + } + + .admin-btn-secondary { + @apply inline-flex h-10 items-center justify-center gap-2 rounded-xl px-5 text-sm font-semibold transition-all duration-150 active:scale-[0.98] border border-white/[0.08] bg-white/[0.04] text-zinc-100 hover:bg-white/[0.08]; + } + + .admin-btn-danger { + @apply inline-flex h-10 items-center justify-center gap-2 rounded-xl px-5 text-sm font-semibold transition-all duration-150 active:scale-[0.98] bg-red-500/10 text-red-400 hover:bg-red-500/20; + } + + .admin-badge { + @apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold; + } + + .admin-label { + @apply text-[11px] font-semibold uppercase tracking-[0.14em] text-zinc-500; + } + + .admin-section-title { + @apply text-[1.6rem] font-bold tracking-tight text-white; + } + + .admin-section-subtitle { + @apply text-sm text-zinc-500; + } + + .admin-num { + @apply font-bold tracking-tight text-white tabular-nums; + } + + /* Compact controls for dense edit rows */ + .admin-input-sm { + @apply h-8 rounded-lg border border-white/[0.08] bg-white/[0.03] px-2.5 text-xs text-white placeholder:text-zinc-500 outline-none transition focus:border-yellow-400/40 focus:bg-white/[0.05]; + } + + .admin-btn-sm { + @apply inline-flex h-7 items-center justify-center gap-1 rounded-lg px-2.5 text-xs font-semibold transition active:scale-[0.97]; + } + + .admin-icon-square { + @apply grid size-9 place-items-center rounded-xl border border-white/[0.08] bg-white/[0.03] text-zinc-300; + } +} + +/* Custom scrollbar for admin tables */ +.admin-scrollbar::-webkit-scrollbar { + height: 6px; + width: 6px; +} + +.admin-scrollbar::-webkit-scrollbar-thumb { + background: rgba(250, 204, 21, 0.5); + border-radius: 999px; +} + +.admin-scrollbar::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.03); +} + +/* Smooth modal fade */ +.modal-fade-enter-active, +.modal-fade-leave-active { + transition: opacity 0.2s ease, transform 0.2s ease; +} + +.modal-fade-enter-from, +.modal-fade-leave-to { + opacity: 0; + transform: scale(0.98); +} diff --git a/app/components/AdminToggle.vue b/app/components/AdminToggle.vue index 0ba8bfa..119b9c2 100644 --- a/app/components/AdminToggle.vue +++ b/app/components/AdminToggle.vue @@ -4,9 +4,9 @@ const model = defineModel({ required: true }) diff --git a/app/composables/useCacheBust.ts b/app/composables/useCacheBust.ts new file mode 100644 index 0000000..509e75d --- /dev/null +++ b/app/composables/useCacheBust.ts @@ -0,0 +1,37 @@ +// Composable để invalidate cached data sau khi edit phim ở admin +const BUST_KEY = 'cinek-cache-version' + +function getCacheVersion(): number { + if (!import.meta.client) return 0 + return Number(localStorage.getItem(BUST_KEY)) || 0 +} + +let currentVersion = getCacheVersion() + +// Tăng version mỗi lần gọi — dùng để invalidate useFetch cache +export function useCacheBust() { + const version = ref(currentVersion) + + function bust() { + if (!import.meta.client) return + currentVersion++ + localStorage.setItem(BUST_KEY, String(currentVersion)) + version.value = currentVersion + // Dispatch event để các component khác biết cần refresh + window.dispatchEvent(new CustomEvent('cinek:cache-bust', { detail: { version: currentVersion } })) + } + + return { version, bust } +} + +// Auto-refresh khi có cache bust từ phía admin +export function useAutoRefresh(onDataUpdated?: () => void) { + const { version } = useCacheBust() + + watch(version, () => { + onDataUpdated?.() + if (import.meta.client) { + window.dispatchEvent(new CustomEvent('cinek:data-updated')) + } + }) +} \ No newline at end of file diff --git a/app/layouts/admin.vue b/app/layouts/admin.vue index 0d43624..08c634a 100644 --- a/app/layouts/admin.vue +++ b/app/layouts/admin.vue @@ -33,42 +33,43 @@ const displayInitial = computed(() => displayName.value.charAt(0).toUpperCase())