From f99ed4f3c32bb194d04ca1fe66335bf9e350e8bc Mon Sep 17 00:00:00 2001 From: Melissa Autumn Date: Thu, 23 May 2024 14:34:32 -0700 Subject: [PATCH] Re-organized admin panels, add invite panel, add admin nav, and fix up data table's null filter handling. --- backend/src/appointment/routes/invite.py | 4 +- frontend/src/components/DataTable.vue | 25 +- frontend/src/elements/ListPagination.vue | 42 +-- frontend/src/elements/admin/AdminNav.vue | 27 ++ frontend/src/locales/en.json | 2 + frontend/src/router.js | 9 +- .../src/views/admin/InviteCodePanelView.vue | 261 ++++++++++++++++++ .../views/{ => admin}/SubscriberPanelView.vue | 8 +- 8 files changed, 339 insertions(+), 39 deletions(-) create mode 100644 frontend/src/elements/admin/AdminNav.vue create mode 100644 frontend/src/views/admin/InviteCodePanelView.vue rename frontend/src/views/{ => admin}/SubscriberPanelView.vue (97%) diff --git a/backend/src/appointment/routes/invite.py b/backend/src/appointment/routes/invite.py index 2dae4faf7..5a6864bda 100644 --- a/backend/src/appointment/routes/invite.py +++ b/backend/src/appointment/routes/invite.py @@ -19,13 +19,13 @@ @router.get('/', response_model=list[schemas.Invite]) -def get_all_invites(db: Session = Depends(get_db), admin: Subscriber = Depends(get_admin_subscriber)): +def get_all_invites(db: Session = Depends(get_db), _admin: Subscriber = Depends(get_admin_subscriber)): """List all existing invites, needs admin permissions""" return db.query(models.Invite).all() @router.post("/generate/{n}", response_model=list[schemas.Invite]) -def generate_invite_codes(n: int, db: Session = Depends(get_db), admin: Subscriber = Depends(get_admin_subscriber)): +def generate_invite_codes(n: int, db: Session = Depends(get_db), _admin: Subscriber = Depends(get_admin_subscriber)): """endpoint to generate n invite codes, needs admin permissions""" return repo.invite.generate_codes(db, n) diff --git a/frontend/src/components/DataTable.vue b/frontend/src/components/DataTable.vue index 0438e1db1..37aed77e1 100644 --- a/frontend/src/components/DataTable.vue +++ b/frontend/src/components/DataTable.vue @@ -13,7 +13,7 @@ @@ -41,9 +41,9 @@ {{ fieldData.value }} - {{ fieldData.value }} - {{ fieldData.value }} - {{ fieldData.value }} + {{ fieldData.value }} + {{ fieldData.value }} + {{ fieldData.value }} @@ -122,13 +122,13 @@ const updatePage = (index) => { const columnSpan = computed(() => (columns.value.length + (allowMultiSelect.value ? 1 : 0))); const selectedFields = ref([]); -const mutableDataList = ref([]); +const mutableDataList = ref(null); /** * Returns either a filtered data list, or the original all nice and paginated */ const paginatedDataList = computed(() => { - if (mutableDataList?.value?.length) { + if (mutableDataList?.value !== null) { return mutableDataList.value.slice(currentPage.value * pageSize, (currentPage.value + 1) * pageSize); } if (dataList?.value?.length) { @@ -138,6 +138,16 @@ const paginatedDataList = computed(() => { return []; }); +const totalDataLength = computed(() => { + if (mutableDataList?.value !== null) { + return mutableDataList.value?.length ?? 0; + } + if (dataList?.value?.length) { + return dataList.value.length; + } + return 0; +}); + const onFieldSelect = (evt, fieldData) => { const isChecked = evt?.target?.checked; @@ -154,8 +164,9 @@ const onFieldSelect = (evt, fieldData) => { const onColumnFilter = (evt, filter) => { mutableDataList.value = filter.fn(evt.target.value, dataList.value); + console.log('Data list info: ', mutableDataList.value, ' vs ', dataList.value); if (mutableDataList.value === dataList.value) { - mutableDataList.value = []; + mutableDataList.value = null; } }; diff --git a/frontend/src/elements/ListPagination.vue b/frontend/src/elements/ListPagination.vue index 313fad212..2fd7f1573 100644 --- a/frontend/src/elements/ListPagination.vue +++ b/frontend/src/elements/ListPagination.vue @@ -1,7 +1,7 @@ @@ -39,19 +39,13 @@ const props = defineProps({ listLength: Number, // number of total items in the displayed list pageSize: Number, // number of items per page }); -const emit = defineEmits(['update']) +const emit = defineEmits(['update']); const currentPage = ref(0); // index of active page -const isFirstPage = computed(() => { - return currentPage.value <= 0; -}); -const pageCount = computed(() => { - return Math.ceil(props.listLength/props.pageSize); -}); -const isLastPage = computed(() => { - return currentPage.value >= pageCount.value-1; -}); +const isFirstPage = computed(() => currentPage.value <= 0); +const pageCount = computed(() => Math.ceil(props.listLength / props.pageSize) || 1); +const isLastPage = computed(() => currentPage.value >= pageCount.value - 1); const prev = () => { if (!isFirstPage.value) { @@ -70,23 +64,15 @@ const goto = (index) => { emit('update', currentPage.value); }; -const showPageItem = (p) => { - return pageCount.value < 6 || p == 1 || p == 2 || isVisibleInnerPage(p) || p == pageCount.value-1; -}; -const showFirstEllipsis = (p) => { - return pageCount.value >= 6 && currentPage.value > 2 && p == 2; -}; -const showPageItemLink = (p) => { - return pageCount.value < 6 || p == 1 || isVisibleInnerPage(p); -}; -const showLastEllipsis = (p) => { - return pageCount.value >= 6 && currentPage.value < pageCount.value-3 && p == pageCount.value-1; -}; +const showPageItem = (p) => pageCount.value < 6 || p == 1 || p == 2 || isVisibleInnerPage(p) || p == pageCount.value - 1; +const showFirstEllipsis = (p) => pageCount.value >= 6 && currentPage.value > 2 && p == 2; +const showPageItemLink = (p) => pageCount.value < 6 || p == 1 || isVisibleInnerPage(p); +const showLastEllipsis = (p) => pageCount.value >= 6 && currentPage.value < pageCount.value - 3 && p == pageCount.value - 1; const isVisibleInnerPage = (p) => (currentPage.value == 0 && p == 3) || ((currentPage.value == 0 || currentPage.value == 1) && p == 4) - || (p > currentPage.value-1 && p < currentPage.value+3) - || ((currentPage.value == pageCount.value-1 || currentPage.value == pageCount.value-2) && p == pageCount.value-3) - || (currentPage.value == pageCount.value-1 && p == pageCount.value-2) + || (p > currentPage.value - 1 && p < currentPage.value + 3) + || ((currentPage.value == pageCount.value - 1 || currentPage.value == pageCount.value - 2) && p == pageCount.value - 3) + || (currentPage.value == pageCount.value - 1 && p == pageCount.value - 2) || p == pageCount.value; diff --git a/frontend/src/elements/admin/AdminNav.vue b/frontend/src/elements/admin/AdminNav.vue new file mode 100644 index 000000000..c3eaca730 --- /dev/null +++ b/frontend/src/elements/admin/AdminNav.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index a8ebaf2dc..daeaffce1 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -76,6 +76,8 @@ "slotIsAvailableAgain": "Time slot now available for bookings." }, "label": { + "admin-invite-codes-panel": "Invites", + "admin-subscriber-panel": "Subscribers", "12hAmPm": "12h AM/PM", "24h": "24h", "DDMMYYYY": "DD/MM/YYYY", diff --git a/frontend/src/router.js b/frontend/src/router.js index 713a367b4..7b7afd9fa 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -14,7 +14,8 @@ const AppointmentsView = defineAsyncComponent(() => import('@/views/Appointments const SettingsView = defineAsyncComponent(() => import('@/views/SettingsView')); const ProfileView = defineAsyncComponent(() => import('@/views/ProfileView')); const LegalView = defineAsyncComponent(() => import('@/views/LegalView')); -const SubscriberPanelView = defineAsyncComponent(() => import('@/views/SubscriberPanelView')); +const SubscriberPanelView = defineAsyncComponent(() => import('@/views/admin/SubscriberPanelView')); +const InviteCodePanelView = defineAsyncComponent(() => import('@/views/admin/InviteCodePanelView.vue')); /** * Defined routes for Thunderbird Appointment @@ -91,11 +92,17 @@ const routes = [ name: 'terms', component: LegalView, }, + // Admin { path: '/admin/subscribers', name: 'admin-subscriber-panel', component: SubscriberPanelView, }, + { + path: '/admin/invites', + name: 'admin-invite-codes-panel', + component: InviteCodePanelView, + }, ]; // create router object to export diff --git a/frontend/src/views/admin/InviteCodePanelView.vue b/frontend/src/views/admin/InviteCodePanelView.vue new file mode 100644 index 000000000..a222e4054 --- /dev/null +++ b/frontend/src/views/admin/InviteCodePanelView.vue @@ -0,0 +1,261 @@ + + + diff --git a/frontend/src/views/SubscriberPanelView.vue b/frontend/src/views/admin/SubscriberPanelView.vue similarity index 97% rename from frontend/src/views/SubscriberPanelView.vue rename to frontend/src/views/admin/SubscriberPanelView.vue index 2bb1e13fa..b59b9466b 100644 --- a/frontend/src/views/SubscriberPanelView.vue +++ b/frontend/src/views/admin/SubscriberPanelView.vue @@ -14,6 +14,9 @@ {{ pageError }} + + +