From f338b9be17fbec0dea99c2a5bd360db626ea5fb8 Mon Sep 17 00:00:00 2001
From: Ahmad Farhat <ahmad.af.farhat@gmail.com>
Date: Fri, 3 Nov 2023 10:43:26 -0400
Subject: [PATCH] Recording view improvements (#5497)

* Added pagination to recording tables

* Make dropdown go up for last item in list

* rspec
---
 .../stylesheets/application.bootstrap.scss     |  4 +---
 app/assets/stylesheets/helpers.scss            |  6 +++---
 app/assets/stylesheets/recordings.scss         |  9 +++++++--
 .../api/v1/recordings_controller.rb            |  2 +-
 app/controllers/api/v1/rooms_controller.rb     |  2 +-
 .../components/admin/manage_users/EditUser.jsx |  2 +-
 .../admin/manage_users/ManageUsers.jsx         |  2 +-
 .../components/admin/roles/EditRole.jsx        |  2 +-
 .../components/admin/roles/Roles.jsx           |  2 +-
 .../admin/room_configuration/RoomConfig.jsx    |  2 +-
 .../server_recordings/ServerRecordings.jsx     |  2 +-
 .../admin/server_rooms/ServerRooms.jsx         |  2 +-
 .../admin/site_settings/SiteSettings.jsx       |  2 +-
 .../components/admin/tenants/Tenants.jsx       |  2 +-
 .../components/recordings/RecordingRow.jsx     |  5 ++++-
 .../components/recordings/RecordingsList.jsx   | 18 +++++++++++++-----
 .../components/recordings/UserRecordings.jsx   |  1 +
 .../room_recordings/RoomRecordings.jsx         |  1 +
 .../room_recordings/RoomsRecordingRow.jsx      |  5 ++++-
 .../utilities/SimpleSelect.jsx                 |  6 ++++--
 spec/controllers/rooms_controller_spec.rb      |  6 +++---
 21 files changed, 52 insertions(+), 31 deletions(-)

diff --git a/app/assets/stylesheets/application.bootstrap.scss b/app/assets/stylesheets/application.bootstrap.scss
index 2446bd5b..8339867f 100644
--- a/app/assets/stylesheets/application.bootstrap.scss
+++ b/app/assets/stylesheets/application.bootstrap.scss
@@ -197,8 +197,6 @@ input.search-bar {
 }
 
 #footer {
-  margin-top: $footer-buffer-height;
-
   #footer-container {
     border-top: 1px solid #d0d5dd;
   }
@@ -464,7 +462,7 @@ input.search-bar {
       box-shadow: 0 0 0 0.25rem var(--brand-color-light) !important;
     }
     &::after {
-      display: none;
+      display: none !important;
     }
   }
 
diff --git a/app/assets/stylesheets/helpers.scss b/app/assets/stylesheets/helpers.scss
index 107604e6..9a30d9c1 100644
--- a/app/assets/stylesheets/helpers.scss
+++ b/app/assets/stylesheets/helpers.scss
@@ -34,14 +34,14 @@
 }
 
 .no-header-height {
-  min-height: calc(100vh - $footer-height - $footer-buffer-height);
+  min-height: calc(100vh - $footer-height);
 }
 
 .regular-height {
-  min-height: calc(100vh - $header-height - $footer-height - $footer-buffer-height);
+  min-height: calc(100vh - $header-height - $footer-height);
 
   .vertical-center {
-    min-height: calc(100vh - $header-height - $header-height - $footer-height - $footer-buffer-height);
+    min-height: calc(100vh - $header-height - $header-height - $footer-height);
   }
 }
 
diff --git a/app/assets/stylesheets/recordings.scss b/app/assets/stylesheets/recordings.scss
index 9b770871..6d401795 100644
--- a/app/assets/stylesheets/recordings.scss
+++ b/app/assets/stylesheets/recordings.scss
@@ -13,10 +13,15 @@
 //
 // You should have received a copy of the GNU Lesser General Public License along
 // with Greenlight; if not, see <http://www.gnu.org/licenses/>.
+#user-recordings {
+  min-height: 699px;
+}
 
-#user-recordings, #room-recordings {
-  min-height: 400px;
+#room-recordings {
+  min-height: 491px;
+}
 
+#user-recordings, #room-recordings {
   table {
     border-top-right-radius: $border-radius-lg;
     border-top-left-radius: $border-radius-lg;
diff --git a/app/controllers/api/v1/recordings_controller.rb b/app/controllers/api/v1/recordings_controller.rb
index 6138fafe..09262909 100644
--- a/app/controllers/api/v1/recordings_controller.rb
+++ b/app/controllers/api/v1/recordings_controller.rb
@@ -37,7 +37,7 @@ module Api
       def index
         sort_config = config_sorting(allowed_columns: %w[name length visibility])
 
-        pagy, recordings = pagy(current_user.recordings&.order(sort_config, recorded_at: :desc)&.search(params[:search]))
+        pagy, recordings = pagy(current_user.recordings&.order(sort_config, recorded_at: :desc)&.search(params[:search]), items: 5)
         render_data data: recordings, meta: pagy_metadata(pagy), status: :ok
       end
 
diff --git a/app/controllers/api/v1/rooms_controller.rb b/app/controllers/api/v1/rooms_controller.rb
index ffde5ff5..e1e4a982 100644
--- a/app/controllers/api/v1/rooms_controller.rb
+++ b/app/controllers/api/v1/rooms_controller.rb
@@ -133,7 +133,7 @@ module Api
       def recordings
         sort_config = config_sorting(allowed_columns: %w[name length visibility])
 
-        pagy, room_recordings = pagy(@room.recordings&.order(sort_config, recorded_at: :desc)&.search(params[:q]))
+        pagy, room_recordings = pagy(@room.recordings&.order(sort_config, recorded_at: :desc)&.search(params[:q]), items: 3)
         render_data data: room_recordings, meta: pagy_metadata(pagy), status: :ok
       end
 
diff --git a/app/javascript/components/admin/manage_users/EditUser.jsx b/app/javascript/components/admin/manage_users/EditUser.jsx
index 364c48c8..fa85878b 100644
--- a/app/javascript/components/admin/manage_users/EditUser.jsx
+++ b/app/javascript/components/admin/manage_users/EditUser.jsx
@@ -39,7 +39,7 @@ export default function EditUser() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5"> { t('admin.admin_panel') } </h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activekey="users">
diff --git a/app/javascript/components/admin/manage_users/ManageUsers.jsx b/app/javascript/components/admin/manage_users/ManageUsers.jsx
index b8269617..f256c9c4 100644
--- a/app/javascript/components/admin/manage_users/ManageUsers.jsx
+++ b/app/javascript/components/admin/manage_users/ManageUsers.jsx
@@ -47,7 +47,7 @@ export default function ManageUsers() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5">{t('admin.admin_panel')}</h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="users">
diff --git a/app/javascript/components/admin/roles/EditRole.jsx b/app/javascript/components/admin/roles/EditRole.jsx
index a546a7e9..883e765b 100644
--- a/app/javascript/components/admin/roles/EditRole.jsx
+++ b/app/javascript/components/admin/roles/EditRole.jsx
@@ -44,7 +44,7 @@ export default function EditRole() {
   if (isLoading) return null;
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5">{ t('admin.admin_panel') }</h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="roles">
diff --git a/app/javascript/components/admin/roles/Roles.jsx b/app/javascript/components/admin/roles/Roles.jsx
index 78175a70..d8ed915a 100644
--- a/app/javascript/components/admin/roles/Roles.jsx
+++ b/app/javascript/components/admin/roles/Roles.jsx
@@ -39,7 +39,7 @@ export default function Roles() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5"> { t('admin.admin_panel') } </h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="roles">
diff --git a/app/javascript/components/admin/room_configuration/RoomConfig.jsx b/app/javascript/components/admin/room_configuration/RoomConfig.jsx
index 3741539b..696a5e1d 100644
--- a/app/javascript/components/admin/room_configuration/RoomConfig.jsx
+++ b/app/javascript/components/admin/room_configuration/RoomConfig.jsx
@@ -37,7 +37,7 @@ export default function RoomConfig() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5"> { t('admin.admin_panel') } </h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="room_configuration">
diff --git a/app/javascript/components/admin/server_recordings/ServerRecordings.jsx b/app/javascript/components/admin/server_recordings/ServerRecordings.jsx
index f51593b8..2b1ad378 100644
--- a/app/javascript/components/admin/server_recordings/ServerRecordings.jsx
+++ b/app/javascript/components/admin/server_recordings/ServerRecordings.jsx
@@ -38,7 +38,7 @@ export default function ServerRecordings() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5"> {t('admin.admin_panel')} </h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="server_recordings">
diff --git a/app/javascript/components/admin/server_rooms/ServerRooms.jsx b/app/javascript/components/admin/server_rooms/ServerRooms.jsx
index c0a69c15..cde5c2e1 100644
--- a/app/javascript/components/admin/server_rooms/ServerRooms.jsx
+++ b/app/javascript/components/admin/server_rooms/ServerRooms.jsx
@@ -44,7 +44,7 @@ export default function ServerRooms() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5"> { t('admin.admin_panel') } </h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="server_rooms">
diff --git a/app/javascript/components/admin/site_settings/SiteSettings.jsx b/app/javascript/components/admin/site_settings/SiteSettings.jsx
index f0821945..a8081e94 100644
--- a/app/javascript/components/admin/site_settings/SiteSettings.jsx
+++ b/app/javascript/components/admin/site_settings/SiteSettings.jsx
@@ -37,7 +37,7 @@ export default function SiteSettings() {
   }
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5">{ t('admin.admin_panel') }</h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="site_settings">
diff --git a/app/javascript/components/admin/tenants/Tenants.jsx b/app/javascript/components/admin/tenants/Tenants.jsx
index 70a1630e..095239f1 100644
--- a/app/javascript/components/admin/tenants/Tenants.jsx
+++ b/app/javascript/components/admin/tenants/Tenants.jsx
@@ -45,7 +45,7 @@ export default function Tenants() {
   const { data: tenants, isLoading } = useTenants({ search: searchInput, page });
 
   return (
-    <div id="admin-panel" className="pb-3">
+    <div id="admin-panel" className="pb-4">
       <h3 className="py-5">{ t('admin.admin_panel') }</h3>
       <Card className="border-0 card-shadow">
         <Tab.Container activeKey="tenants">
diff --git a/app/javascript/components/recordings/RecordingRow.jsx b/app/javascript/components/recordings/RecordingRow.jsx
index a465a27d..e6497890 100644
--- a/app/javascript/components/recordings/RecordingRow.jsx
+++ b/app/javascript/components/recordings/RecordingRow.jsx
@@ -35,7 +35,7 @@ import SimpleSelect from '../shared_components/utilities/SimpleSelect';
 
 // TODO: Amir - Refactor this.
 export default function RecordingRow({
-  recording, visibilityMutation: useVisibilityAPI, deleteMutation: useDeleteAPI, adminTable,
+  recording, visibilityMutation: useVisibilityAPI, deleteMutation: useDeleteAPI, adminTable, dropUp,
 }) {
   const { t } = useTranslation();
 
@@ -104,6 +104,7 @@ export default function RecordingRow({
       <td className="border-0">
         <SimpleSelect
           defaultValue={recording.visibility}
+          dropUp={dropUp}
         >
           <Dropdown.Item
             key="Public/Protected"
@@ -205,6 +206,7 @@ export default function RecordingRow({
 
 RecordingRow.defaultProps = {
   adminTable: false,
+  dropUp: false,
 };
 
 RecordingRow.propTypes = {
@@ -227,4 +229,5 @@ RecordingRow.propTypes = {
   visibilityMutation: PropTypes.func.isRequired,
   deleteMutation: PropTypes.func.isRequired,
   adminTable: PropTypes.bool,
+  dropUp: PropTypes.bool,
 };
diff --git a/app/javascript/components/recordings/RecordingsList.jsx b/app/javascript/components/recordings/RecordingsList.jsx
index fbec10b4..0dde058e 100644
--- a/app/javascript/components/recordings/RecordingsList.jsx
+++ b/app/javascript/components/recordings/RecordingsList.jsx
@@ -28,7 +28,7 @@ import SearchBar from '../shared_components/search/SearchBar';
 import ProcessingRecordingRow from './ProcessingRecordingRow';
 
 export default function RecordingsList({
-  recordings, isLoading, setPage, searchInput, setSearchInput, recordingsProcessing, adminTable,
+  recordings, isLoading, setPage, searchInput, setSearchInput, recordingsProcessing, adminTable, numPlaceholders,
 }) {
   const { t } = useTranslation();
 
@@ -65,14 +65,19 @@ export default function RecordingsList({
                 <tbody className="border-top-0">
                   {[...Array(recordingsProcessing)].map(() => <ProcessingRecordingRow />)}
                   {
-                    (isLoading && [...Array(7)].map((val, idx) => (
+                    (isLoading && [...Array(numPlaceholders)].map((val, idx) => (
                       // eslint-disable-next-line react/no-array-index-key
                       <RecordingsListRowPlaceHolder key={idx} />
                     )))
                   }
                   {
-                    (recordings?.data?.length > 0 && recordings?.data?.map((recording) => (
-                      <RoomsRecordingRow key={recording.id} recording={recording} adminTable={adminTable} />
+                    (recordings?.data?.length > 0 && recordings?.data?.map((recording, idx) => (
+                      <RoomsRecordingRow
+                        key={recording.id}
+                        recording={recording}
+                        adminTable={adminTable}
+                        dropUp={(recordings?.meta?.page || 0) * (recordings?.meta?.items || 0) - 1 === idx}
+                      />
                     )))
                   }
                 </tbody>
@@ -99,10 +104,11 @@ export default function RecordingsList({
 }
 
 RecordingsList.defaultProps = {
-  recordings: { data: [], meta: { page: 1, pages: 1 } },
+  recordings: { data: [], meta: { page: 1, pages: 1, items: 3 } },
   recordingsProcessing: 0,
   searchInput: '',
   adminTable: false,
+  numPlaceholders: 7,
 };
 
 RecordingsList.propTypes = {
@@ -124,6 +130,7 @@ RecordingsList.propTypes = {
     meta: PropTypes.shape({
       page: PropTypes.number,
       pages: PropTypes.number,
+      items: PropTypes.number,
     }),
   }),
   isLoading: PropTypes.bool.isRequired,
@@ -132,4 +139,5 @@ RecordingsList.propTypes = {
   setSearchInput: PropTypes.func.isRequired,
   recordingsProcessing: PropTypes.number,
   adminTable: PropTypes.bool,
+  numPlaceholders: PropTypes.number,
 };
diff --git a/app/javascript/components/recordings/UserRecordings.jsx b/app/javascript/components/recordings/UserRecordings.jsx
index ff8afc65..cf43a520 100644
--- a/app/javascript/components/recordings/UserRecordings.jsx
+++ b/app/javascript/components/recordings/UserRecordings.jsx
@@ -31,6 +31,7 @@ export default function UserRecordings() {
         setPage={setPage}
         setSearchInput={setSearchInput}
         searchInput={searchInput}
+        numPlaceholders={5}
       />
     </div>
   );
diff --git a/app/javascript/components/recordings/room_recordings/RoomRecordings.jsx b/app/javascript/components/recordings/room_recordings/RoomRecordings.jsx
index 687d2390..4de37a69 100644
--- a/app/javascript/components/recordings/room_recordings/RoomRecordings.jsx
+++ b/app/javascript/components/recordings/room_recordings/RoomRecordings.jsx
@@ -36,6 +36,7 @@ export default function RoomRecordings() {
         setSearchInput={setSearchInput}
         searchInput={searchInput}
         recordingsProcessing={roomRecordingsProcessing.data}
+        numPlaceholders={3}
       />
     </div>
   );
diff --git a/app/javascript/components/recordings/room_recordings/RoomsRecordingRow.jsx b/app/javascript/components/recordings/room_recordings/RoomsRecordingRow.jsx
index 7a2b3d35..40ecc5e9 100644
--- a/app/javascript/components/recordings/room_recordings/RoomsRecordingRow.jsx
+++ b/app/javascript/components/recordings/room_recordings/RoomsRecordingRow.jsx
@@ -20,19 +20,21 @@ import useUpdateRecordingVisibility from '../../../hooks/mutations/recordings/us
 import useDeleteRecording from '../../../hooks/mutations/recordings/useDeleteRecording';
 import RecordingRow from '../RecordingRow';
 
-export default function RoomsRecordingRow({ recording, adminTable }) {
+export default function RoomsRecordingRow({ recording, adminTable, dropUp }) {
   return (
     <RecordingRow
       adminTable={adminTable}
       recording={recording}
       visibilityMutation={useUpdateRecordingVisibility}
       deleteMutation={useDeleteRecording}
+      dropUp={dropUp}
     />
   );
 }
 
 RoomsRecordingRow.defaultProps = {
   adminTable: false,
+  dropUp: false,
 };
 
 RoomsRecordingRow.propTypes = {
@@ -51,4 +53,5 @@ RoomsRecordingRow.propTypes = {
     map: PropTypes.func,
   }).isRequired,
   adminTable: PropTypes.bool,
+  dropUp: PropTypes.bool,
 };
diff --git a/app/javascript/components/shared_components/utilities/SimpleSelect.jsx b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx
index 7ec44282..6fd162fc 100644
--- a/app/javascript/components/shared_components/utilities/SimpleSelect.jsx
+++ b/app/javascript/components/shared_components/utilities/SimpleSelect.jsx
@@ -19,12 +19,12 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { ChevronDownIcon } from '@heroicons/react/20/solid';
 
-export default function SimpleSelect({ defaultValue, children }) {
+export default function SimpleSelect({ defaultValue, dropUp, children }) {
   // Get the currently selected option and set the dropdown toggle to that value
   const defaultString = children?.filter((item) => item.props.value === defaultValue)[0];
 
   return (
-    <Dropdown className="simple-select">
+    <Dropdown className="simple-select" drop={dropUp ? 'up' : undefined}>
       <Dropdown.Toggle>
         { defaultString?.props?.children }
         <ChevronDownIcon className="hi-s float-end" />
@@ -38,10 +38,12 @@ export default function SimpleSelect({ defaultValue, children }) {
 
 SimpleSelect.defaultProps = {
   defaultValue: '',
+  dropUp: false,
   children: undefined,
 };
 
 SimpleSelect.propTypes = {
   defaultValue: PropTypes.string,
+  dropUp: PropTypes.bool,
   children: PropTypes.arrayOf(PropTypes.element),
 };
diff --git a/spec/controllers/rooms_controller_spec.rb b/spec/controllers/rooms_controller_spec.rb
index 469cbc07..db67b943 100644
--- a/spec/controllers/rooms_controller_spec.rb
+++ b/spec/controllers/rooms_controller_spec.rb
@@ -298,8 +298,8 @@ RSpec.describe Api::V1::RoomsController, type: :controller do
     it 'returns recordings belonging to the room' do
       room1 = create(:room, user:, friendly_id: 'friendly_id_1')
       room2 = create(:room, user:, friendly_id: 'friendly_id_2')
-      recordings = create_list(:recording, 5, room: room1)
-      create_list(:recording, 5, room: room2)
+      recordings = create_list(:recording, 3, room: room1)
+      create_list(:recording, 3, room: room2)
       get :recordings, params: { friendly_id: room1.friendly_id }
       recording_ids = response.parsed_body['data'].pluck('id')
       expect(response).to have_http_status(:ok)
@@ -320,7 +320,7 @@ RSpec.describe Api::V1::RoomsController, type: :controller do
       create(:shared_access, user_id: shared_user.id, room_id: room.id)
       sign_in_user(shared_user)
 
-      recordings = create_list(:recording, 5, room:)
+      recordings = create_list(:recording, 3, room:)
 
       get :recordings, params: { friendly_id: room.friendly_id }
 
-- 
GitLab