Skip to content

Commit b3f1a6f

Browse files
FEATURE (databases): Add adaptivity for mobile databases
1 parent d521e2a commit b3f1a6f

File tree

11 files changed

+920
-571
lines changed

11 files changed

+920
-571
lines changed

frontend/src/features/backups/ui/BackupsComponent.tsx

Lines changed: 253 additions & 179 deletions
Large diffs are not rendered by default.

frontend/src/features/backups/ui/EditBackupConfigComponent.tsx

Lines changed: 144 additions & 132 deletions
Large diffs are not rendered by default.

frontend/src/features/databases/ui/DatabaseConfigComponent.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ export const DatabaseConfigComponent = ({
147147
};
148148

149149
return (
150-
<div className="w-full rounded-tr-md rounded-br-md rounded-bl-md bg-white p-5 shadow">
150+
<div className="w-full rounded-tr-md rounded-br-md rounded-bl-md bg-white p-3 shadow sm:p-5">
151151
{!isEditName ? (
152-
<div className="mb-5 flex items-center text-2xl font-bold">
152+
<div className="mb-5 flex items-center text-xl font-bold sm:text-2xl">
153153
{database.name}
154154

155155
{isCanManageDBs && (
@@ -162,7 +162,7 @@ export const DatabaseConfigComponent = ({
162162
<div>
163163
<div className="flex items-center">
164164
<Input
165-
className="max-w-[250px]"
165+
className="max-w-full sm:max-w-[250px]"
166166
value={editDatabase?.name}
167167
onChange={(e) => {
168168
if (!editDatabase) return;
@@ -174,7 +174,7 @@ export const DatabaseConfigComponent = ({
174174
size="large"
175175
/>
176176

177-
<div className="ml-1 flex items-center">
177+
<div className="ml-1 flex flex-shrink-0 items-center">
178178
<Button
179179
type="text"
180180
className="flex h-6 w-6 items-center justify-center p-0"
@@ -204,7 +204,7 @@ export const DatabaseConfigComponent = ({
204204
)}
205205

206206
{database.lastBackupErrorMessage && (
207-
<div className="max-w-[400px] rounded border border-red-600 px-3 py-3">
207+
<div className="mb-4 max-w-full rounded border border-red-600 px-3 py-3 sm:max-w-[400px]">
208208
<div className="mt-1 flex items-center text-sm font-bold text-red-600">
209209
<InfoCircleOutlined className="mr-2" style={{ color: 'red' }} />
210210
Last backup error
@@ -226,8 +226,8 @@ export const DatabaseConfigComponent = ({
226226
</div>
227227
)}
228228

229-
<div className="flex flex-wrap gap-10">
230-
<div className="w-[400px]">
229+
<div className="flex flex-col gap-6 lg:flex-row lg:flex-wrap lg:gap-10">
230+
<div className="w-full lg:w-[400px]">
231231
<div className="mt-5 flex items-center font-bold">
232232
<div>Database settings</div>
233233

@@ -260,7 +260,7 @@ export const DatabaseConfigComponent = ({
260260
</div>
261261
</div>
262262

263-
<div className="w-[400px]">
263+
<div className="w-full lg:w-[400px]">
264264
<div className="mt-5 flex items-center font-bold">
265265
<div>Backup config</div>
266266

@@ -299,8 +299,8 @@ export const DatabaseConfigComponent = ({
299299
</div>
300300
</div>
301301

302-
<div className="flex flex-wrap gap-10">
303-
<div className="w-[400px]">
302+
<div className="flex flex-col gap-6 lg:flex-row lg:flex-wrap lg:gap-10">
303+
<div className="w-full lg:w-[400px]">
304304
<div className="mt-5 flex items-center font-bold">
305305
<div>Healthcheck settings</div>
306306

@@ -328,7 +328,7 @@ export const DatabaseConfigComponent = ({
328328
</div>
329329
</div>
330330

331-
<div className="w-[400px]">
331+
<div className="w-full lg:w-[400px]">
332332
<div className="mt-5 flex items-center font-bold">
333333
<div>Notifiers settings</div>
334334

@@ -366,10 +366,10 @@ export const DatabaseConfigComponent = ({
366366
</div>
367367

368368
{!isEditDatabaseSpecificDataSettings && (
369-
<div className="mt-10">
369+
<div className="mt-10 flex flex-col gap-2 sm:flex-row sm:gap-0">
370370
<Button
371371
type="primary"
372-
className="mr-1"
372+
className="w-full sm:mr-1 sm:w-auto"
373373
ghost
374374
onClick={testConnection}
375375
loading={isTestingConnection}
@@ -380,7 +380,7 @@ export const DatabaseConfigComponent = ({
380380

381381
<Button
382382
type="primary"
383-
className="mr-1"
383+
className="w-full sm:mr-1 sm:w-auto"
384384
ghost
385385
onClick={copyDatabase}
386386
loading={isCopying}
@@ -391,6 +391,7 @@ export const DatabaseConfigComponent = ({
391391

392392
<Button
393393
type="primary"
394+
className="w-full sm:w-auto"
394395
danger
395396
onClick={() => setIsShowRemoveConfirm(true)}
396397
ghost

frontend/src/features/databases/ui/DatabasesComponent.tsx

Lines changed: 77 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
44
import { databaseApi } from '../../../entity/databases';
55
import type { Database } from '../../../entity/databases';
66
import type { WorkspaceResponse } from '../../../entity/workspaces';
7+
import { useIsMobile } from '../../../shared/hooks';
78
import { CreateDatabaseComponent } from './CreateDatabaseComponent';
89
import { DatabaseCardComponent } from './DatabaseCardComponent';
910
import { DatabaseComponent } from './DatabaseComponent';
@@ -17,6 +18,7 @@ interface Props {
1718
const SELECTED_DATABASE_STORAGE_KEY = 'selectedDatabaseId';
1819

1920
export const DatabasesComponent = ({ contentHeight, workspace, isCanManageDBs }: Props) => {
21+
const isMobile = useIsMobile();
2022
const [isLoading, setIsLoading] = useState(true);
2123
const [databases, setDatabases] = useState<Database[]>([]);
2224
const [searchQuery, setSearchQuery] = useState('');
@@ -44,7 +46,8 @@ export const DatabasesComponent = ({ contentHeight, workspace, isCanManageDBs }:
4446
setDatabases(databases);
4547
if (selectDatabaseId) {
4648
updateSelectedDatabaseId(selectDatabaseId);
47-
} else if (!selectedDatabaseId && !isSilent) {
49+
} else if (!selectedDatabaseId && !isSilent && !isMobile) {
50+
// On desktop, auto-select a database; on mobile, keep it unselected
4851
const savedDatabaseId = localStorage.getItem(
4952
`${SELECTED_DATABASE_STORAGE_KEY}_${workspace.id}`,
5053
);
@@ -87,66 +90,86 @@ export const DatabasesComponent = ({ contentHeight, workspace, isCanManageDBs }:
8790
database.name.toLowerCase().includes(searchQuery.toLowerCase()),
8891
);
8992

93+
// On mobile, show either the list or the database details
94+
const showDatabaseList = !isMobile || !selectedDatabaseId;
95+
const showDatabaseDetails = selectedDatabaseId && (!isMobile || selectedDatabaseId);
96+
9097
return (
9198
<>
9299
<div className="flex grow">
93-
<div
94-
className="mx-3 w-[250px] min-w-[250px] overflow-y-auto pr-2"
95-
style={{ height: contentHeight }}
96-
>
97-
{databases.length >= 5 && (
98-
<>
99-
{isCanManageDBs && addDatabaseButton}
100+
{showDatabaseList && (
101+
<div
102+
className="w-full overflow-y-auto md:mx-3 md:w-[250px] md:min-w-[250px] md:pr-2"
103+
style={{ height: contentHeight }}
104+
>
105+
{databases.length >= 5 && (
106+
<>
107+
{isCanManageDBs && addDatabaseButton}
108+
109+
<div className="mb-2">
110+
<input
111+
placeholder="Search database"
112+
value={searchQuery}
113+
onChange={(e) => setSearchQuery(e.target.value)}
114+
className="w-full border-b border-gray-300 p-1 text-gray-500 outline-none"
115+
/>
116+
</div>
117+
</>
118+
)}
119+
120+
{filteredDatabases.length > 0
121+
? filteredDatabases.map((database) => (
122+
<DatabaseCardComponent
123+
key={database.id}
124+
database={database}
125+
selectedDatabaseId={selectedDatabaseId}
126+
setSelectedDatabaseId={updateSelectedDatabaseId}
127+
/>
128+
))
129+
: searchQuery && (
130+
<div className="mb-4 text-center text-sm text-gray-500">
131+
No databases found matching &quot;{searchQuery}&quot;
132+
</div>
133+
)}
134+
135+
{databases.length < 5 && isCanManageDBs && addDatabaseButton}
136+
137+
<div className="mx-3 text-center text-xs text-gray-500">
138+
Database - is a thing we are backing up
139+
</div>
140+
</div>
141+
)}
100142

143+
{showDatabaseDetails && (
144+
<div className="flex w-full flex-col md:flex-1">
145+
{isMobile && (
101146
<div className="mb-2">
102-
<input
103-
placeholder="Search database"
104-
value={searchQuery}
105-
onChange={(e) => setSearchQuery(e.target.value)}
106-
className="w-full border-b border-gray-300 p-1 text-gray-500 outline-none"
107-
/>
147+
<Button
148+
type="default"
149+
onClick={() => updateSelectedDatabaseId(undefined)}
150+
className="w-full"
151+
>
152+
← Back to databases
153+
</Button>
108154
</div>
109-
</>
110-
)}
111-
112-
{filteredDatabases.length > 0
113-
? filteredDatabases.map((database) => (
114-
<DatabaseCardComponent
115-
key={database.id}
116-
database={database}
117-
selectedDatabaseId={selectedDatabaseId}
118-
setSelectedDatabaseId={updateSelectedDatabaseId}
119-
/>
120-
))
121-
: searchQuery && (
122-
<div className="mb-4 text-center text-sm text-gray-500">
123-
No databases found matching &quot;{searchQuery}&quot;
124-
</div>
125-
)}
126-
127-
{databases.length < 5 && isCanManageDBs && addDatabaseButton}
128-
129-
<div className="mx-3 text-center text-xs text-gray-500">
130-
Database - is a thing we are backing up
155+
)}
156+
157+
<DatabaseComponent
158+
contentHeight={isMobile ? contentHeight - 50 : contentHeight}
159+
databaseId={selectedDatabaseId}
160+
onDatabaseChanged={() => {
161+
loadDatabases();
162+
}}
163+
onDatabaseDeleted={() => {
164+
const remainingDatabases = databases.filter(
165+
(database) => database.id !== selectedDatabaseId,
166+
);
167+
updateSelectedDatabaseId(remainingDatabases[0]?.id);
168+
loadDatabases();
169+
}}
170+
isCanManageDBs={isCanManageDBs}
171+
/>
131172
</div>
132-
</div>
133-
134-
{selectedDatabaseId && (
135-
<DatabaseComponent
136-
contentHeight={contentHeight}
137-
databaseId={selectedDatabaseId}
138-
onDatabaseChanged={() => {
139-
loadDatabases();
140-
}}
141-
onDatabaseDeleted={() => {
142-
const remainingDatabases = databases.filter(
143-
(database) => database.id !== selectedDatabaseId,
144-
);
145-
updateSelectedDatabaseId(remainingDatabases[0]?.id);
146-
loadDatabases();
147-
}}
148-
isCanManageDBs={isCanManageDBs}
149-
/>
150173
)}
151174
</div>
152175

frontend/src/features/healthcheck/ui/HealthckeckAttemptsComponent.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,16 @@ export const HealthckeckAttemptsComponent = ({ database }: Props) => {
118118
}
119119

120120
return (
121-
<div className="w-full rounded-tr-md rounded-br-md rounded-bl-md bg-white p-5 shadow">
122-
<h2 className="text-xl font-bold">Healthcheck attempts</h2>
121+
<div className="w-full rounded-tr-md rounded-br-md rounded-bl-md bg-white p-3 shadow sm:p-5">
122+
<h2 className="text-lg font-bold sm:text-xl">Healthcheck attempts</h2>
123123

124-
<div className="mt-4 flex items-center gap-2">
125-
<span className="mr-2 text-sm font-medium">Period</span>
124+
<div className="mt-3 flex flex-col gap-2 sm:mt-4 sm:flex-row sm:items-center">
125+
<span className="text-sm font-medium sm:mr-2">Period</span>
126126
<Select
127127
size="small"
128128
value={period}
129129
onChange={(value) => setPeriod(value)}
130-
style={{ width: 120 }}
130+
className="w-full sm:w-[120px]"
131131
options={[
132132
{ value: 'today', label: 'Today' },
133133
{ value: '7d', label: '7 days' },
@@ -137,7 +137,7 @@ export const HealthckeckAttemptsComponent = ({ database }: Props) => {
137137
/>
138138
</div>
139139

140-
<div className="mt-5" />
140+
<div className="mt-4 sm:mt-5" />
141141

142142
{isLoading ? (
143143
<div className="flex justify-center">

frontend/src/features/healthcheck/ui/ShowHealthcheckConfigComponent.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,31 @@ export const ShowHealthcheckConfigComponent = ({ databaseId }: Props) => {
4141
<div className="space-y-4">
4242
<div className="mb-1 flex items-center">
4343
<div className="min-w-[180px]">Is health check enabled</div>
44-
<div className="w-[250px]">{healthcheckConfig.isHealthcheckEnabled ? 'Yes' : 'No'}</div>
44+
<div>{healthcheckConfig.isHealthcheckEnabled ? 'Yes' : 'No'}</div>
4545
</div>
4646

4747
{healthcheckConfig.isHealthcheckEnabled && (
4848
<>
4949
<div className="mb-1 flex items-center">
5050
<div className="min-w-[180px]">Notify when unavailable</div>
51-
<div className="w-[250px]">
51+
<div className="lg:w-[200px]">
5252
{healthcheckConfig.isSentNotificationWhenUnavailable ? 'Yes' : 'No'}
5353
</div>
5454
</div>
5555

5656
<div className="mb-1 flex items-center">
5757
<div className="min-w-[180px]">Check interval (minutes)</div>
58-
<div className="w-[250px]">{healthcheckConfig.intervalMinutes}</div>
58+
<div className="lg:w-[200px]">{healthcheckConfig.intervalMinutes}</div>
5959
</div>
6060

6161
<div className="mb-1 flex items-center">
6262
<div className="min-w-[180px]">Attempts before down</div>
63-
<div className="w-[250px]">{healthcheckConfig.attemptsBeforeConcideredAsDown}</div>
63+
<div className="lg:w-[200px]">{healthcheckConfig.attemptsBeforeConcideredAsDown}</div>
6464
</div>
6565

6666
<div className="mb-1 flex items-center">
6767
<div className="min-w-[180px]">Store attempts (days)</div>
68-
<div className="w-[250px]">{healthcheckConfig.storeAttemptsDays}</div>
68+
<div className="lg:w-[200px]">{healthcheckConfig.storeAttemptsDays}</div>
6969
</div>
7070
</>
7171
)}

frontend/src/shared/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { useScreenHeight } from './useScreenHeight';
2+
export { useIsMobile } from './useIsMobile';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useEffect, useState } from 'react';
2+
3+
/**
4+
* This hook detects if the current device is mobile (screen width <= 768px)
5+
* and adjusts dynamically when the window is resized.
6+
*
7+
* @returns isMobile boolean
8+
*/
9+
export function useIsMobile(): boolean {
10+
const [isMobile, setIsMobile] = useState<boolean>(false);
11+
12+
useEffect(() => {
13+
const updateIsMobile = () => {
14+
setIsMobile(window.innerWidth <= 768);
15+
};
16+
17+
updateIsMobile(); // Set initial value
18+
window.addEventListener('resize', updateIsMobile);
19+
20+
return () => {
21+
window.removeEventListener('resize', updateIsMobile);
22+
};
23+
}, []);
24+
25+
return isMobile;
26+
}

0 commit comments

Comments
 (0)