mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 01:35:25 +00:00
Compare commits
40 Commits
Implement-
...
firebase_p
Author | SHA1 | Date | |
---|---|---|---|
df29aab111 | |||
138390496c | |||
46a7add90d | |||
73de1e6ff9 | |||
826dea8054 | |||
fdea4b1cd0 | |||
823d86fd80 | |||
dd735032ea | |||
6dcc851d97 | |||
15b36fd052 | |||
a4024067c7 | |||
95cded4bf5 | |||
757a96ed9f | |||
b857736e10 | |||
1fccd51440 | |||
c07ddb0ccd | |||
58e99f95b2 | |||
227df6fe3d | |||
9451ec0cc4 | |||
fc797c2646 | |||
318e1d9af7 | |||
d47dc349bc | |||
c221c8499f | |||
71cf4b9feb | |||
c43cf9347f | |||
9990b1805e | |||
009b7c0316 | |||
779c0fe916 | |||
e448eabda6 | |||
9dfb3ed369 | |||
63353af38b | |||
68b6c9b18c | |||
fa6ee9a0af | |||
3601b02bc3 | |||
fdd0526c78 | |||
bdeec7d325 | |||
50ff17a0c1 | |||
87c2e3261d | |||
62a6f9c993 | |||
f7e4d6ff07 |
@ -1,2 +1,3 @@
|
||||
ENV_NAME=development
|
||||
BASE_URL=https://syncrow-dev.azurewebsites.net
|
||||
BASE_URL=https://syncrow-dev.azurewebsites.net
|
||||
RTDB_URL=https://syncrow-dev-79446.asia-southeast1.firebasedatabase.app/
|
||||
|
@ -1,2 +1,3 @@
|
||||
ENV_NAME=production
|
||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||
RTDB_URL=https://syncrow-prod-79446.asia-southeast1.firebasedatabase.app/
|
||||
|
@ -1,2 +1,3 @@
|
||||
ENV_NAME=staging
|
||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||
BASE_URL=https://syncrow-staging.azurewebsites.net
|
||||
RTDB_URL=https://syncrow-staging-79446.asia-southeast1.firebasedatabase.app/
|
||||
|
112
.vscode/launch.json
vendored
112
.vscode/launch.json
vendored
@ -1,67 +1,49 @@
|
||||
{
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
|
||||
"name": "DEVELOPMENT",
|
||||
|
||||
"request": "launch",
|
||||
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
||||
},{
|
||||
|
||||
"name": "STAGING",
|
||||
|
||||
"request": "launch",
|
||||
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_staging.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
||||
},{
|
||||
|
||||
"name": "PRODUCTION",
|
||||
|
||||
"request": "launch",
|
||||
|
||||
"type": "dart",
|
||||
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
||||
},
|
||||
|
||||
]
|
||||
"configurations": [
|
||||
{
|
||||
"name": "DEVELOPMENT",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
"--web-experimental-hot-reload"
|
||||
],
|
||||
"flutterMode": "debug"
|
||||
},
|
||||
{
|
||||
"name": "STAGING",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_staging.dart",
|
||||
"--web-experimental-hot-reload"
|
||||
],
|
||||
"flutterMode": "debug"
|
||||
},
|
||||
{
|
||||
"name": "PRODUCTION",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"-d",
|
||||
"chrome",
|
||||
"--web-port",
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main.dart",
|
||||
"--web-experimental-hot-reload"
|
||||
],
|
||||
"flutterMode": "debug"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_9795_9381)">
|
||||
<path d="M9.21875 13.5149V10.7805H6.48438C6.05286 10.7805 5.70312 10.4308 5.70312 9.99924C5.70312 9.56787 6.05286 9.21799 6.48438 9.21799H9.21875V6.48361C9.21875 6.05225 9.56848 5.70236 10 5.70236C10.4315 5.70236 10.7812 6.05225 10.7812 6.48361V9.21799H13.5156C13.9471 9.21799 14.2969 9.56787 14.2969 9.99924C14.2969 10.4308 13.9471 10.7805 13.5156 10.7805H10.7812V13.5149C10.7812 13.9464 10.4315 14.2961 10 14.2961C9.56848 14.2961 9.21875 13.9464 9.21875 13.5149ZM17.0711 2.92892C15.1823 1.04019 12.6711 0 10 0C7.32895 0 4.81766 1.04019 2.92892 2.92892C1.04019 4.81766 0 7.32895 0 10C0 12.6711 1.04019 15.1823 2.92892 17.0711C4.81766 18.9598 7.32895 20 10 20C11.8286 20 13.6179 19.5016 15.1743 18.5588C15.5434 18.3353 15.6613 17.8549 15.4378 17.486C15.2142 17.1169 14.7337 16.9989 14.3648 17.2224C13.0525 18.0173 11.5431 18.4375 10 18.4375C5.3476 18.4375 1.5625 14.6524 1.5625 10C1.5625 5.3476 5.3476 1.5625 10 1.5625C14.6524 1.5625 18.4375 5.3476 18.4375 10C18.4375 11.6637 17.9428 13.2829 17.0068 14.6831C16.767 15.0417 16.8634 15.5269 17.2221 15.7668C17.5807 16.0065 18.0659 15.91 18.3058 15.5515C19.4141 13.8936 20 11.9739 20 10C20 7.32895 18.9598 4.81766 17.0711 2.92892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9795_9381">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 20C15.514 20 19.9998 15.514 19.9998 9.99995C19.9998 4.48604 15.514 0 10 0C4.48613 0 0.000183105 4.48604 0.000183105 9.99995C0.000183105 15.514 4.48613 20 10 20ZM10 1.36892C14.7591 1.36892 18.6309 5.24077 18.631 9.99995C18.631 14.7591 14.7592 18.631 10 18.6311C5.24095 18.631 1.36919 14.7591 1.36919 9.99986C1.36919 5.24086 5.24095 1.36892 10 1.36892Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
<path d="M8.65713 14.2828C8.92444 14.55 9.35784 14.5499 9.62505 14.2828C9.89245 14.0154 9.89245 13.5821 9.62496 13.3147L6.99481 10.6846L14.6112 10.6839C14.9892 10.6838 15.2956 10.3775 15.2956 9.99926C15.2955 9.62126 14.9891 9.31499 14.6111 9.31499L6.99444 9.31572L9.62523 6.68511C9.89254 6.41781 9.89254 5.98432 9.62523 5.7171C9.49154 5.5835 9.3164 5.5166 9.14118 5.5166C8.96605 5.5166 8.79092 5.5835 8.65722 5.71701L4.85811 9.51604C4.7297 9.64435 4.65761 9.81838 4.65761 9.99999C4.6577 10.1816 4.7298 10.3555 4.8582 10.4841L8.65713 14.2828Z" fill="#023DFE" fill-opacity="0.7"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.9999 0C4.48595 0 0 4.48586 0 9.99971C0 15.514 4.48595 20 9.9999 20C15.5138 20 19.9996 15.5139 19.9996 9.99971C19.9996 4.48586 15.5138 0 9.9999 0ZM9.9999 18.5665C5.27638 18.5665 1.43349 14.7234 1.43349 9.99971C1.43349 5.27628 5.27638 1.43349 9.9999 1.43349C14.7233 1.43349 18.5661 5.27628 18.5661 9.99971C18.5661 14.7234 14.7233 18.5665 9.9999 18.5665Z" fill="#D5D5D5"/>
|
||||
<path d="M15.1416 9.83211H10.4423V4.69526C10.4423 4.29943 10.1215 3.97852 9.72553 3.97852C9.3297 3.97852 9.00879 4.29943 9.00879 4.69526V10.5489C9.00879 10.9447 9.3297 11.2656 9.72553 11.2656H15.1416C15.5376 11.2656 15.8584 10.9447 15.8584 10.5489C15.8584 10.153 15.5375 9.83211 15.1416 9.83211Z" fill="#D5D5D5"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 799 B |
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 18 KiB |
@ -1 +1 @@
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"test2-8a3d2","configurations":{"android":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","ios":"1:427332280600:ios:14346b200780dc760c7e6d","macos":"1:427332280600:ios:14346b200780dc760c7e6d","web":"1:427332280600:web:ad50516a87a35a1a0c7e6d","windows":"1:427332280600:web:f7a25537ccd5a7bd0c7e6d"}}}}}}
|
||||
{"flutter":{"platforms":{"android":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:android:2bc36fbe82994a3e0c7e6d","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"test2-8a3d2","appId":"1:427332280600:ios:14346b200780dc760c7e6d","uploadDebugSymbols":true,"fileOutput":"macos/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"syncrow-prod-79446","configurations":{"web":"1:255001682464:web:a03e2d6214c13101561245"}}}}}}
|
16
lib/firebase_options.dart
Normal file
16
lib/firebase_options.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
|
||||
final class DefaultFirebaseOptions extends FirebaseOptions {
|
||||
const DefaultFirebaseOptions({
|
||||
required String databaseUrl,
|
||||
}) : super(
|
||||
apiKey: 'AIzaSyDgq5ywsnFVbbQO-Xz1Z4sR5bBcuiDaS9g',
|
||||
appId: '1:255001682464:web:a03e2d6214c13101561245',
|
||||
messagingSenderId: '255001682464',
|
||||
projectId: 'syncrow-prod-79446',
|
||||
authDomain: 'syncrow-prod-79446.firebaseapp.com',
|
||||
storageBucket: 'syncrow-prod-79446.firebasestorage.app',
|
||||
databaseURL: databaseUrl,
|
||||
measurementId: 'G-1850Q89RMK',
|
||||
);
|
||||
}
|
@ -1,93 +0,0 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptionsDev {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
return macos;
|
||||
case TargetPlatform.windows:
|
||||
return windows;
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyCVEvKsJYzhWDFM-9Od68FE0nPpP933st0',
|
||||
appId: '1:427332280600:web:ad50516a87a35a1a0c7e6d',
|
||||
messagingSenderId: '427332280600',
|
||||
projectId: 'test2-8a3d2',
|
||||
authDomain: 'test2-8a3d2.firebaseapp.com',
|
||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
||||
measurementId: 'G-Z1RTTTV5H9',
|
||||
);
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyA5qOErxdm0zJmoHIB0TixfebYEsNRpwV0',
|
||||
appId: '1:427332280600:android:2bc36fbe82994a3e0c7e6d',
|
||||
messagingSenderId: '427332280600',
|
||||
projectId: 'test2-8a3d2',
|
||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
|
||||
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
|
||||
messagingSenderId: '427332280600',
|
||||
projectId: 'test2-8a3d2',
|
||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
||||
iosBundleId: 'com.example.syncrowWeb',
|
||||
);
|
||||
|
||||
static const FirebaseOptions macos = FirebaseOptions(
|
||||
apiKey: 'AIzaSyABnpH6yo2RRjtkp4PlvtK84hKwRm2DhBw',
|
||||
appId: '1:427332280600:ios:14346b200780dc760c7e6d',
|
||||
messagingSenderId: '427332280600',
|
||||
projectId: 'test2-8a3d2',
|
||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
||||
iosBundleId: 'com.example.syncrowWeb',
|
||||
);
|
||||
|
||||
static const FirebaseOptions windows = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDizKjPC5rdkEjDxwXjM-RU5unB0Ziq3iw',
|
||||
appId: '1:427332280600:web:f7a25537ccd5a7bd0c7e6d',
|
||||
messagingSenderId: '427332280600',
|
||||
projectId: 'test2-8a3d2',
|
||||
authDomain: 'test2-8a3d2.firebaseapp.com',
|
||||
databaseURL: 'https://test2-8a3d2-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'test2-8a3d2.firebasestorage.app',
|
||||
measurementId: 'G-4LFVXEXWKY',
|
||||
);
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
// File generated by FlutterFire CLI.
|
||||
// ignore_for_file: type=lint
|
||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||
|
||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// import 'firebase_options.dart';
|
||||
/// // ...
|
||||
/// await Firebase.initializeApp(
|
||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||
/// );
|
||||
/// ```
|
||||
class DefaultFirebaseOptionsStaging {
|
||||
static FirebaseOptions get currentPlatform {
|
||||
if (kIsWeb) {
|
||||
return web;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return android;
|
||||
case TargetPlatform.iOS:
|
||||
return ios;
|
||||
case TargetPlatform.macOS:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for macos - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.windows:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for windows - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
case TargetPlatform.linux:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions have not been configured for linux - '
|
||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||
);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'DefaultFirebaseOptions are not supported for this platform.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static const FirebaseOptions android = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDP9GpYfLE8gHTj3kZ1hW8fx_FkJqOqSQk',
|
||||
appId: '1:786692570726:android:0ef7079c2b978d4417b7a7',
|
||||
messagingSenderId: '786692570726',
|
||||
projectId: 'syncrow-staging',
|
||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'syncrow-staging.appspot.com',
|
||||
);
|
||||
|
||||
static const FirebaseOptions ios = FirebaseOptions(
|
||||
apiKey: 'AIzaSyAWlRiuJ75FMlf2_UDdri1voWKvkaSHtRg',
|
||||
appId: '1:786692570726:ios:455a6fcff77e130f17b7a7',
|
||||
messagingSenderId: '786692570726',
|
||||
projectId: 'syncrow-staging',
|
||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'syncrow-staging.appspot.com',
|
||||
iosBundleId: 'com.example.syncrow.app',
|
||||
);
|
||||
|
||||
static const FirebaseOptions web = FirebaseOptions(
|
||||
apiKey: 'AIzaSyDyGaQ3sZhb4meaY6sGke-YglhdhJ2is8Q',
|
||||
appId: '1:786692570726:web:93c931e6701797b317b7a7',
|
||||
messagingSenderId: '786692570726',
|
||||
projectId: 'syncrow-staging',
|
||||
authDomain: 'syncrow-staging.firebaseapp.com',
|
||||
databaseURL: 'https://syncrow-staging-default-rtdb.firebaseio.com',
|
||||
storageBucket: 'syncrow-staging.appspot.com',
|
||||
measurementId: 'G-CZ3J3G6LMQ',
|
||||
);
|
||||
}
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
@ -27,7 +27,9 @@ Future<void> main() async {
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
||||
options: DefaultFirebaseOptions(
|
||||
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||
),
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options_dev.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
@ -27,7 +27,9 @@ Future<void> main() async {
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptionsDev.currentPlatform,
|
||||
options: DefaultFirebaseOptions(
|
||||
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||
),
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/firebase_options_prod.dart';
|
||||
import 'package:syncrow_web/firebase_options.dart';
|
||||
import 'package:syncrow_web/pages/auth/bloc/auth_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_event.dart';
|
||||
@ -24,7 +24,9 @@ Future<void> main() async {
|
||||
await dotenv.load(fileName: '.env.$environment');
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
||||
options: DefaultFirebaseOptions(
|
||||
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||
),
|
||||
);
|
||||
initialSetup();
|
||||
} catch (_) {}
|
||||
@ -56,7 +58,7 @@ class MyApp extends StatelessWidget {
|
||||
BlocProvider<CreateRoutineBloc>(
|
||||
create: (context) => CreateRoutineBloc(),
|
||||
),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
||||
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||
BlocProvider<VisitorPasswordBloc>(
|
||||
create: (context) => VisitorPasswordBloc(),
|
||||
),
|
||||
|
@ -1,14 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class BookingPage extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
const BookingPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
const BookingPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -38,10 +33,8 @@ class BookingPage extends StatelessWidget {
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.homeIcon,
|
||||
label: 'Manage Bookable Spaces',
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(2);
|
||||
}),
|
||||
const SizedBox(width: 20),
|
||||
onPressed: () {}),
|
||||
SizedBox(width: 20),
|
||||
SvgTextButton(
|
||||
svgAsset: Assets.groupIcon,
|
||||
label: 'Manage Users',
|
||||
|
@ -12,13 +12,10 @@ class SvgTextButton extends StatelessWidget {
|
||||
final double borderRadius;
|
||||
final List<BoxShadow> boxShadow;
|
||||
final double svgSize;
|
||||
final double? fontSize;
|
||||
final FontWeight? fontWeight;
|
||||
|
||||
const SvgTextButton({
|
||||
super.key,
|
||||
required this.svgAsset,
|
||||
this.fontSize,
|
||||
this.fontWeight,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
this.backgroundColor = ColorsManager.circleRolesBackground,
|
||||
@ -63,8 +60,8 @@ class SvgTextButton extends StatelessWidget {
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: labelColor,
|
||||
fontSize: fontSize ?? 16,
|
||||
fontWeight: fontWeight ?? FontWeight.w500,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,73 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class DummyNonNookableSpaces implements NonBookableSpacesService {
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) {
|
||||
return Future.value(PaginatedDataModel<BookableSpacemodel>(
|
||||
data: [
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'saturday'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 6, minute: 20),
|
||||
cost: 6,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual1',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: ['wed', 'saturday', 'thuresday'],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 5, minute: 20),
|
||||
cost: 5,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual2',
|
||||
),
|
||||
BookableSpacemodel(
|
||||
spaceName: 'space3',
|
||||
spaceConfig: BookableSpaceConfig(
|
||||
configUuid: 'uuid',
|
||||
bookableDays: [
|
||||
'saturday',
|
||||
'sunday',
|
||||
'Monday',
|
||||
'tuesday',
|
||||
'wed',
|
||||
'thuresday'
|
||||
],
|
||||
availability: true,
|
||||
bookingEndTime: const TimeOfDay(hour: 13, minute: 20),
|
||||
bookingStartTime: const TimeOfDay(hour: 15, minute: 20),
|
||||
cost: 2,
|
||||
),
|
||||
spaceUuid: 'uuiiddd',
|
||||
spaceVirtualAddress: 'idvirtual3',
|
||||
)
|
||||
],
|
||||
page: 1,
|
||||
size: 1,
|
||||
hasNext: false,
|
||||
totalPages: 0,
|
||||
totalItems: 0,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteBookableSpacesService implements BookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteBookableSpacesService(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
queryParameters: {
|
||||
'configured': true,
|
||||
'page': param.currentPage,
|
||||
},
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteNonBookableSpaces implements NonBookableSpacesService {
|
||||
final HTTPService _httpService;
|
||||
RemoteNonBookableSpaces(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Spaces';
|
||||
|
||||
@override
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
queryParameters: {
|
||||
'configured': false,
|
||||
'page': params.currentPage,
|
||||
'search': params.searchedWords,
|
||||
},
|
||||
expectedResponseModel: (json) {
|
||||
final result = json as Map<String, dynamic>;
|
||||
return PaginatedDataModel.fromJson(
|
||||
result,
|
||||
BookableSpacemodel.fromJsonList,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams params) async {
|
||||
try {
|
||||
await _httpService.post(
|
||||
path: ApiEndpoints.bookableSpaces,
|
||||
body: params.toJson(),
|
||||
expectedResponseModel: (p0) {},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||
|
||||
class RemoteUpdateBookableSpaceService implements UpdateBookableSpaceService {
|
||||
final HTTPService _httpService;
|
||||
RemoteUpdateBookableSpaceService(this._httpService);
|
||||
static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
|
||||
@override
|
||||
Future<BookableSpaceConfig> update(
|
||||
UpdateBookableSpaceParam updateParam) async {
|
||||
try {
|
||||
final response = await _httpService.put(
|
||||
path: '${ApiEndpoints.bookableSpaces}/${updateParam.spaceUuid}',
|
||||
body: updateParam.toJson(),
|
||||
expectedResponseModel: (json) {
|
||||
return BookableSpaceConfig.fromJson(
|
||||
json['data'] as Map<String, dynamic>);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
final message = e.response?.data as Map<String, dynamic>?;
|
||||
final error = message?['error'] as Map<String, dynamic>?;
|
||||
final errorMessage = error?['error'] as String? ?? '';
|
||||
final formattedErrorMessage = [
|
||||
_defaultErrorMessage,
|
||||
errorMessage,
|
||||
].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
} catch (e) {
|
||||
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BookableSpaceConfig {
|
||||
String configUuid;
|
||||
List<String> bookableDays;
|
||||
TimeOfDay? bookingStartTime;
|
||||
TimeOfDay? bookingEndTime;
|
||||
int cost;
|
||||
bool availability;
|
||||
BookableSpaceConfig({
|
||||
required this.configUuid,
|
||||
required this.availability,
|
||||
required this.bookableDays,
|
||||
this.bookingEndTime,
|
||||
this.bookingStartTime,
|
||||
required this.cost,
|
||||
});
|
||||
factory BookableSpaceConfig.zero() => BookableSpaceConfig(
|
||||
configUuid: '',
|
||||
bookableDays: [],
|
||||
availability: false,
|
||||
cost: -1,
|
||||
);
|
||||
factory BookableSpaceConfig.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpaceConfig(
|
||||
configUuid: json['uuid'] as String,
|
||||
bookableDays: (json['daysAvailable'] as List).cast<String>(),
|
||||
availability: (json['active'] as bool?) ?? false,
|
||||
bookingEndTime: parseTimeOfDay(json['startTime'] as String),
|
||||
bookingStartTime: parseTimeOfDay(json['endTime'] as String),
|
||||
cost: json['points'] as int,
|
||||
);
|
||||
|
||||
static TimeOfDay parseTimeOfDay(String timeString) {
|
||||
final parts = timeString.split(':');
|
||||
final hour = int.parse(parts[0]);
|
||||
final minute = int.parse(parts[1]);
|
||||
return TimeOfDay(hour: hour, minute: minute);
|
||||
}
|
||||
|
||||
bool get isValid =>
|
||||
bookableDays.isNotEmpty &&
|
||||
cost >= 0 &&
|
||||
bookingStartTime != null &&
|
||||
bookingEndTime != null;
|
||||
|
||||
BookableSpaceConfig copyWith({
|
||||
List<String>? bookableDays,
|
||||
TimeOfDay? bookingStartTime,
|
||||
TimeOfDay? bookingEndTime,
|
||||
int? cost,
|
||||
bool? availability,
|
||||
}) {
|
||||
return BookableSpaceConfig(
|
||||
configUuid: configUuid,
|
||||
availability: availability ?? this.availability,
|
||||
bookableDays: bookableDays ?? this.bookableDays,
|
||||
cost: cost ?? this.cost,
|
||||
bookingEndTime: bookingEndTime ?? this.bookingEndTime,
|
||||
bookingStartTime: bookingStartTime ?? this.bookingStartTime,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
|
||||
class BookableSpacemodel {
|
||||
String spaceUuid;
|
||||
String spaceName;
|
||||
BookableSpaceConfig? spaceConfig;
|
||||
String spaceVirtualAddress;
|
||||
|
||||
BookableSpacemodel({
|
||||
required this.spaceUuid,
|
||||
required this.spaceName,
|
||||
this.spaceConfig,
|
||||
required this.spaceVirtualAddress,
|
||||
});
|
||||
factory BookableSpacemodel.zero() => BookableSpacemodel(
|
||||
spaceUuid: '',
|
||||
spaceName: '',
|
||||
spaceVirtualAddress: '',
|
||||
);
|
||||
factory BookableSpacemodel.fromJson(Map<String, dynamic> json) =>
|
||||
BookableSpacemodel(
|
||||
spaceUuid: json['uuid'] as String,
|
||||
spaceName: json['spaceName'] as String,
|
||||
spaceConfig: json['bookableConfig'] == null
|
||||
? BookableSpaceConfig.zero()
|
||||
: BookableSpaceConfig.fromJson(
|
||||
json['bookableConfig'] as Map<String, dynamic>),
|
||||
spaceVirtualAddress: json['virtualLocation'] as String,
|
||||
);
|
||||
|
||||
static List<BookableSpacemodel> fromJsonList(List<dynamic> jsonList) =>
|
||||
jsonList
|
||||
.map(
|
||||
(e) => BookableSpacemodel.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList();
|
||||
|
||||
bool get isValid =>
|
||||
spaceUuid.isNotEmpty &&
|
||||
spaceName.isNotEmpty &&
|
||||
spaceVirtualAddress.isNotEmpty &&
|
||||
spaceConfig != null &&
|
||||
spaceConfig!.isValid;
|
||||
|
||||
BookableSpacemodel copyWith({
|
||||
String? spaceUuid,
|
||||
String? spaceName,
|
||||
BookableSpaceConfig? spaceConfig,
|
||||
String? spaceVirtualAddress,
|
||||
}) {
|
||||
return BookableSpacemodel(
|
||||
spaceUuid: spaceUuid ?? this.spaceUuid,
|
||||
spaceName: spaceName ?? this.spaceName,
|
||||
spaceConfig: spaceConfig ?? this.spaceConfig,
|
||||
spaceVirtualAddress: spaceVirtualAddress ?? this.spaceVirtualAddress,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
class BookableSpacesParams {
|
||||
int currentPage;
|
||||
BookableSpacesParams({
|
||||
required this.currentPage,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
class NonBookableSpacesParams {
|
||||
int currentPage;
|
||||
String? searchedWords;
|
||||
NonBookableSpacesParams({
|
||||
required this.currentPage,
|
||||
this.searchedWords,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'page': currentPage,
|
||||
};
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
class UpdateBookableSpaceParam {
|
||||
String spaceUuid;
|
||||
|
||||
List<String>? bookableDays;
|
||||
String? bookingStartTime;
|
||||
String? bookingEndTime;
|
||||
int? cost;
|
||||
bool? availability;
|
||||
UpdateBookableSpaceParam({
|
||||
required this.spaceUuid,
|
||||
this.bookingStartTime,
|
||||
this.bookingEndTime,
|
||||
this.bookableDays,
|
||||
this.availability,
|
||||
this.cost,
|
||||
});
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (bookableDays != null) 'daysAvailable': bookableDays,
|
||||
if (bookingStartTime != null) 'startTime': bookingStartTime,
|
||||
if (bookingEndTime != null) 'endTime': bookingEndTime,
|
||||
if (cost != null) 'points': cost,
|
||||
if (availability != null) 'active': availability,
|
||||
};
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class BookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
BookableSpacesParams param);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
abstract class NonBookableSpacesService {
|
||||
Future<PaginatedDataModel<BookableSpacemodel>> load(
|
||||
NonBookableSpacesParams params);
|
||||
Future<void> sendBookableSpacesToApi(SendBookableSpacesToApiParams params);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class SendBookableSpacesToApiParams {
|
||||
List<String> spaceUuids;
|
||||
List<String> daysAvailable;
|
||||
String startTime;
|
||||
String endTime;
|
||||
int points;
|
||||
SendBookableSpacesToApiParams({
|
||||
required this.spaceUuids,
|
||||
required this.daysAvailable,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.points,
|
||||
});
|
||||
|
||||
static SendBookableSpacesToApiParams fromBookableSpacesModel(
|
||||
List<BookableSpacemodel> bookableSpaces) {
|
||||
return SendBookableSpacesToApiParams(
|
||||
spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(),
|
||||
daysAvailable: bookableSpaces
|
||||
.expand((space) => space.spaceConfig!.bookableDays)
|
||||
.toSet()
|
||||
.toList(),
|
||||
startTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig!.bookingStartTime!),
|
||||
endTime: formatTimeOfDayTo24HourString(
|
||||
bookableSpaces.first.spaceConfig!.bookingEndTime!),
|
||||
points: bookableSpaces.first.spaceConfig!.cost,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'spaceUuids': spaceUuids,
|
||||
'daysAvailable': daysAvailable,
|
||||
'startTime': startTime,
|
||||
'endTime': endTime,
|
||||
'points': points
|
||||
};
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
|
||||
abstract class UpdateBookableSpaceService {
|
||||
Future<BookableSpaceConfig> update(UpdateBookableSpaceParam updateParam);
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'bookable_spaces_event.dart';
|
||||
part 'bookable_spaces_state.dart';
|
||||
|
||||
class BookableSpacesBloc
|
||||
extends Bloc<BookableSpacesEvent, BookableSpacesState> {
|
||||
final BookableSpacesService bookableSpacesService;
|
||||
BookableSpacesBloc(this.bookableSpacesService)
|
||||
: super(BookableSpacesInitial()) {
|
||||
on<LoadBookableSpacesEvent>(_onLoadBookableSpaces);
|
||||
on<InsertUpdatedSpaceEvent>(_onInsertUpdatedSpaceEven);
|
||||
}
|
||||
|
||||
Future<void> _onLoadBookableSpaces(
|
||||
LoadBookableSpacesEvent event, Emitter<BookableSpacesState> emit) async {
|
||||
emit(BookableSpacesLoading());
|
||||
try {
|
||||
final bookableSpaces = await bookableSpacesService.load(event.params);
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: bookableSpaces));
|
||||
} on APIException catch (e) {
|
||||
emit(BookableSpacesError(error: e.message));
|
||||
} catch (e) {
|
||||
emit(
|
||||
BookableSpacesError(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onInsertUpdatedSpaceEven(
|
||||
InsertUpdatedSpaceEvent event, Emitter<BookableSpacesState> emit) {
|
||||
emit(InsertingUpdatedSpaceState());
|
||||
|
||||
if (event.bookableSpace.spaceConfig!.configUuid ==
|
||||
event.updatedBookableSpaceConfig.configUuid) {
|
||||
final editedBookableSpace = event.bookableSpaces.data.firstWhere(
|
||||
(element) => element.spaceUuid == event.bookableSpace.spaceUuid,
|
||||
);
|
||||
final config = editedBookableSpace.spaceConfig!.copyWith(
|
||||
availability: event.updatedBookableSpaceConfig.availability,
|
||||
bookableDays: event.updatedBookableSpaceConfig.bookableDays,
|
||||
bookingEndTime: event.updatedBookableSpaceConfig.bookingEndTime,
|
||||
bookingStartTime: event.updatedBookableSpaceConfig.bookingStartTime,
|
||||
cost: event.updatedBookableSpaceConfig.cost,
|
||||
);
|
||||
editedBookableSpace.spaceConfig = config;
|
||||
final index = event.bookableSpaces.data.indexWhere(
|
||||
(element) => element.spaceUuid == event.bookableSpace.spaceUuid,
|
||||
);
|
||||
event.bookableSpaces.data.removeAt(index);
|
||||
event.bookableSpaces.data.insert(index, editedBookableSpace);
|
||||
}
|
||||
|
||||
emit(BookableSpacesLoaded(bookableSpacesList: event.bookableSpaces));
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesEvent extends Equatable {
|
||||
const BookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadBookableSpacesEvent extends BookableSpacesEvent {
|
||||
final BookableSpacesParams params;
|
||||
const LoadBookableSpacesEvent(this.params);
|
||||
}
|
||||
|
||||
class InsertUpdatedSpaceEvent extends BookableSpacesEvent {
|
||||
final PaginatedDataModel<BookableSpacemodel> bookableSpaces;
|
||||
final BookableSpacemodel bookableSpace;
|
||||
final BookableSpaceConfig updatedBookableSpaceConfig;
|
||||
const InsertUpdatedSpaceEvent({
|
||||
required this.bookableSpaces,
|
||||
required this.bookableSpace,
|
||||
required this.updatedBookableSpaceConfig,
|
||||
});
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
part of 'bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class BookableSpacesState extends Equatable {
|
||||
const BookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class BookableSpacesInitial extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoading extends BookableSpacesState {}
|
||||
|
||||
final class BookableSpacesLoaded extends BookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> bookableSpacesList;
|
||||
const BookableSpacesLoaded({
|
||||
required this.bookableSpacesList,
|
||||
});
|
||||
}
|
||||
|
||||
final class BookableSpacesError extends BookableSpacesState {
|
||||
final String error;
|
||||
const BookableSpacesError({
|
||||
required this.error,
|
||||
});
|
||||
}
|
||||
|
||||
class InsertingUpdatedSpaceState extends BookableSpacesState {}
|
@ -1,18 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'toggle_points_switch_state.dart';
|
||||
|
||||
class TogglePointsSwitchCubit extends Cubit<TogglePointsSwitchState> {
|
||||
TogglePointsSwitchCubit() : super(TogglePointsSwitchInitial());
|
||||
bool switchValue = true;
|
||||
void activateSwitch() {
|
||||
switchValue = true;
|
||||
emit(ActivatePointsSwitch());
|
||||
}
|
||||
|
||||
void unActivateSwitch() {
|
||||
switchValue = false;
|
||||
emit(UnActivatePointsSwitch());
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
part of 'toggle_points_switch_cubit.dart';
|
||||
|
||||
sealed class TogglePointsSwitchState extends Equatable {
|
||||
const TogglePointsSwitchState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class TogglePointsSwitchInitial extends TogglePointsSwitchState {}
|
||||
|
||||
class ActivatePointsSwitch extends TogglePointsSwitchState {}
|
||||
|
||||
class UnActivatePointsSwitch extends TogglePointsSwitchState {}
|
@ -1,136 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
part 'non_bookaable_spaces_event.dart';
|
||||
part 'non_bookaable_spaces_state.dart';
|
||||
|
||||
class NonBookableSpacesBloc
|
||||
extends Bloc<NonBookableSpacesEvent, NonBookableSpacesState> {
|
||||
NonBookableSpacesService nonBookableSpacesService;
|
||||
List<BookableSpacemodel> selectedBookableSpaces = [];
|
||||
NonBookableSpacesBloc(this.nonBookableSpacesService)
|
||||
: super(NonBookableSpacesInitial()) {
|
||||
on<LoadUnBookableSpacesEvent>(_onLoadUnBookableSpacesEvent);
|
||||
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
|
||||
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
|
||||
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
|
||||
}
|
||||
|
||||
TimeOfDay? get endTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
|
||||
|
||||
TimeOfDay? get startTime =>
|
||||
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
|
||||
Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currState = state as NonBookableSpacesLoaded;
|
||||
try {
|
||||
emit(NonBookableSpacesLoading(
|
||||
lastNonBookableSpaces: currState.nonBookableSpaces));
|
||||
|
||||
final nonBookableSpacesList = await nonBookableSpacesService.load(
|
||||
event.nonBookableSpacesParams,
|
||||
);
|
||||
nonBookableSpacesList.data.addAll(currState.nonBookableSpaces.data);
|
||||
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: nonBookableSpacesList,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
emit(const NonBookableSpacesLoading());
|
||||
final nonBookableSpacesList = await nonBookableSpacesService.load(
|
||||
event.nonBookableSpacesParams,
|
||||
);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onAddToBookableSpaceEvent(
|
||||
AddToBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit,
|
||||
) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
emit(AddNonBookableSpaceIntoBookableState());
|
||||
final updatedSelectedSpaces =
|
||||
List<BookableSpacemodel>.from(currentState.selectedBookableSpaces)
|
||||
..add(event.nonBookableSpace);
|
||||
|
||||
selectedBookableSpaces.add(event.nonBookableSpace);
|
||||
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: updatedSelectedSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onRemoveFromBookableSpaceEvent(RemoveFromBookableSpaceEvent event,
|
||||
Emitter<NonBookableSpacesState> emit) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
final currentState = state as NonBookableSpacesLoaded;
|
||||
emit(RemoveBookableSpaceIntoNonBookableState());
|
||||
if (currentState.selectedBookableSpaces.isNotEmpty) {
|
||||
currentState.selectedBookableSpaces.remove(event.bookableSpace);
|
||||
}
|
||||
selectedBookableSpaces.remove(event.bookableSpace);
|
||||
emit(
|
||||
NonBookableSpacesLoaded(
|
||||
nonBookableSpaces: currentState.nonBookableSpaces,
|
||||
selectedBookableSpaces: currentState.selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
|
||||
Emitter<NonBookableSpacesState> emit) async {
|
||||
emit(const NonBookableSpacesLoading());
|
||||
try {
|
||||
await nonBookableSpacesService.sendBookableSpacesToApi(
|
||||
SendBookableSpacesToApiParams.fromBookableSpacesModel(
|
||||
selectedBookableSpaces,
|
||||
),
|
||||
);
|
||||
emit(NonBookableSpacesInitial());
|
||||
} catch (e) {
|
||||
emit(
|
||||
NonBookableSpacesError(e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onCheckConfigurValidityEvent(
|
||||
CheckConfigurValidityEvent event, Emitter<NonBookableSpacesState> emit) {
|
||||
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
|
||||
emit(ValidSaveButtonState());
|
||||
} else {
|
||||
emit(UnValidSaveButtonState());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesEvent extends Equatable {
|
||||
const NonBookableSpacesEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadUnBookableSpacesEvent extends NonBookableSpacesEvent {
|
||||
final NonBookableSpacesParams nonBookableSpacesParams;
|
||||
const LoadUnBookableSpacesEvent({
|
||||
required this.nonBookableSpacesParams,
|
||||
});
|
||||
}
|
||||
|
||||
class AddToBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
const AddToBookableSpaceEvent({
|
||||
required this.nonBookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent {
|
||||
final BookableSpacemodel bookableSpace;
|
||||
const RemoveFromBookableSpaceEvent({
|
||||
required this.bookableSpace,
|
||||
});
|
||||
}
|
||||
|
||||
class SendBookableSpacesToApi extends NonBookableSpacesEvent {}
|
||||
|
||||
class CheckConfigurValidityEvent extends NonBookableSpacesEvent {}
|
@ -1,39 +0,0 @@
|
||||
part of 'non_bookaable_spaces_bloc.dart';
|
||||
|
||||
sealed class NonBookableSpacesState extends Equatable {
|
||||
const NonBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class NonBookableSpacesInitial extends NonBookableSpacesState {}
|
||||
|
||||
class NonBookableSpacesLoading extends NonBookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel>? lastNonBookableSpaces;
|
||||
const NonBookableSpacesLoading({
|
||||
this.lastNonBookableSpaces,
|
||||
});
|
||||
}
|
||||
|
||||
class NonBookableSpacesLoaded extends NonBookableSpacesState {
|
||||
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
|
||||
final List<BookableSpacemodel> selectedBookableSpaces;
|
||||
const NonBookableSpacesLoaded({
|
||||
required this.nonBookableSpaces,
|
||||
this.selectedBookableSpaces = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class NonBookableSpacesError extends NonBookableSpacesState {
|
||||
final String error;
|
||||
const NonBookableSpacesError(this.error);
|
||||
}
|
||||
|
||||
class AddNonBookableSpaceIntoBookableState extends NonBookableSpacesState {}
|
||||
|
||||
class RemoveBookableSpaceIntoNonBookableState extends NonBookableSpacesState {}
|
||||
|
||||
class ValidSaveButtonState extends NonBookableSpacesState {}
|
||||
|
||||
class UnValidSaveButtonState extends NonBookableSpacesState {}
|
@ -1,18 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'steps_state.dart';
|
||||
|
||||
class StepsCubit extends Cubit<StepsState> {
|
||||
StepsCubit() : super(StepsInitial());
|
||||
|
||||
void initDialogValue() {
|
||||
emit(StepOneState());
|
||||
}
|
||||
|
||||
void goToNextStep() {
|
||||
if (state is StepOneState) {
|
||||
emit(StepTwoState());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
part of 'steps_cubit.dart';
|
||||
|
||||
sealed class StepsState extends Equatable {
|
||||
const StepsState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class StepsInitial extends StepsState {}
|
||||
|
||||
final class StepOneState extends StepsState {}
|
||||
|
||||
final class StepTwoState extends StepsState {}
|
||||
|
||||
final class StepEditMode extends StepsState {}
|
@ -1,35 +0,0 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
part 'update_bookable_spaces_event.dart';
|
||||
part 'update_bookable_spaces_state.dart';
|
||||
|
||||
class UpdateBookableSpacesBloc
|
||||
extends Bloc<UpdateBookableSpaceEvent, UpdateBookableSpacesState> {
|
||||
final UpdateBookableSpaceService updateBookableSpaceService;
|
||||
UpdateBookableSpacesBloc(this.updateBookableSpaceService)
|
||||
: super(UpdateBookableSpacesInitial()) {
|
||||
on<UpdateBookableSpace>(_onUpdateBookableSpace);
|
||||
}
|
||||
|
||||
Future<void> _onUpdateBookableSpace(UpdateBookableSpace event,
|
||||
Emitter<UpdateBookableSpacesState> emit) async {
|
||||
emit(UpdateBookableSpaceLoading(event.updatedParams.spaceUuid));
|
||||
try {
|
||||
final updatedSpace =
|
||||
await updateBookableSpaceService.update(event.updatedParams);
|
||||
|
||||
emit(UpdateBookableSpaceSuccess(bookableSpaceConfig: updatedSpace));
|
||||
} on APIException catch (e) {
|
||||
emit(UpdateBookableSpaceFailure(error: e.message));
|
||||
} catch (e) {
|
||||
emit(
|
||||
UpdateBookableSpaceFailure(error: e.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
part of 'update_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class UpdateBookableSpaceEvent extends Equatable {
|
||||
const UpdateBookableSpaceEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class UpdateBookableSpace extends UpdateBookableSpaceEvent {
|
||||
final UpdateBookableSpaceParam updatedParams;
|
||||
const UpdateBookableSpace({
|
||||
required this.updatedParams,
|
||||
});
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
part of 'update_bookable_spaces_bloc.dart';
|
||||
|
||||
sealed class UpdateBookableSpacesState extends Equatable {
|
||||
const UpdateBookableSpacesState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class UpdateBookableSpacesInitial extends UpdateBookableSpacesState {}
|
||||
|
||||
final class UpdateBookableSpaceLoading extends UpdateBookableSpacesState {
|
||||
final String updatingSpaceUuid;
|
||||
|
||||
const UpdateBookableSpaceLoading(this.updatingSpaceUuid);
|
||||
}
|
||||
|
||||
final class UpdateBookableSpaceSuccess extends UpdateBookableSpacesState {
|
||||
final BookableSpaceConfig bookableSpaceConfig;
|
||||
const UpdateBookableSpaceSuccess({
|
||||
required this.bookableSpaceConfig,
|
||||
});
|
||||
}
|
||||
|
||||
final class UpdateBookableSpaceFailure extends UpdateBookableSpacesState {
|
||||
final String error;
|
||||
const UpdateBookableSpaceFailure({
|
||||
required this.error,
|
||||
});
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_update_bookable_space_service.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/bottom_pagination_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/table_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/top_part_widget.dart';
|
||||
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class ManageBookableSpacesPage extends StatefulWidget {
|
||||
final PageController pageController;
|
||||
const ManageBookableSpacesPage({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ManageBookableSpacesPage> createState() =>
|
||||
_ManageBookableSpacesPageState();
|
||||
}
|
||||
|
||||
class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => BookableSpacesBloc(
|
||||
RemoteBookableSpacesService(HTTPService()),
|
||||
)..add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => UpdateBookableSpacesBloc(
|
||||
RemoteUpdateBookableSpaceService(HTTPService()),
|
||||
),
|
||||
)
|
||||
],
|
||||
child: ManageBookableSpacesWidget(
|
||||
pageController: widget.pageController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageBookableSpacesWidget extends StatelessWidget {
|
||||
final PageController pageController;
|
||||
|
||||
const ManageBookableSpacesWidget({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 10, child: TopPartWidget(pageController: pageController)),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const Expanded(
|
||||
flex: 85,
|
||||
child: TablePartWidget(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
const Expanded(
|
||||
flex: 5,
|
||||
child: BottomPaginationPartWidget(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SetupBookableSpacesDialog extends StatelessWidget {
|
||||
final TextEditingController pointsController = TextEditingController();
|
||||
SetupBookableSpacesDialog({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<StepsCubit>(
|
||||
create: (context) => StepsCubit()..initDialogValue(),
|
||||
),
|
||||
BlocProvider<NonBookableSpacesBloc>(
|
||||
create: (context) => NonBookableSpacesBloc(
|
||||
RemoteNonBookableSpaces(HTTPService()),
|
||||
)..add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: AlertDialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Center(
|
||||
child: Text(
|
||||
'Set Up a Bookable Spaces',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.dialogBlueTitle,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
content: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Divider(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: StepperPartWidget(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 588,
|
||||
child: VerticalDivider(
|
||||
thickness: 0.5,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: DetailsStepsWidget(
|
||||
pointsController: pointsController,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Builder(builder: (context) {
|
||||
final stepsState = context.watch<StepsCubit>().state;
|
||||
final nonBookableBloc = context.watch<NonBookableSpacesBloc>();
|
||||
final selectedSpaces = nonBookableBloc.selectedBookableSpaces;
|
||||
return stepsState is StepOneState
|
||||
? NextFirstStepButton(selectedSpaces: selectedSpaces)
|
||||
: SaveSecondStepButton(
|
||||
selectedSpaces: selectedSpaces,
|
||||
pointsController: pointsController);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/string_utils.dart';
|
||||
|
||||
class BookingPeriodWidget extends StatelessWidget {
|
||||
const BookingPeriodWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
),
|
||||
const Text('Booking Period'),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
width: 300,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: ColorsManager.graysColor,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
TimePickerWidget(
|
||||
title: 'Start Time',
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc =
|
||||
context.read<NonBookableSpacesBloc>();
|
||||
|
||||
if (nonBookableBloc.endTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
timePicked, nonBookableBloc.endTime!)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
"You can't choose start Time Before End time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingStartTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Icon(
|
||||
Icons.arrow_right_alt,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
TimePickerWidget(
|
||||
title: 'End Time',
|
||||
onTimePicked: (timePicked) {
|
||||
if (timePicked == null) {
|
||||
return;
|
||||
}
|
||||
final nonBookableBloc =
|
||||
context.read<NonBookableSpacesBloc>();
|
||||
if (nonBookableBloc.startTime != null &&
|
||||
isEndTimeAfterStartTime(
|
||||
nonBookableBloc.startTime!, timePicked)) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
"You can't choose End Time After Start time"),
|
||||
duration: Duration(seconds: 2),
|
||||
backgroundColor: ColorsManager.red,
|
||||
));
|
||||
throw Exception();
|
||||
} else {
|
||||
nonBookableBloc.selectedBookableSpaces.forEach(
|
||||
(e) => e.spaceConfig!.bookingEndTime = timePicked,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Container(
|
||||
width: 50,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(10),
|
||||
bottomLeft: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SvgPicture.asset(
|
||||
Assets.clockIcon,
|
||||
height: 15,
|
||||
color: ColorsManager.blackColor.withValues(alpha: 0.4),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonsDividerBottomDialogWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final void Function()? onNextPressed;
|
||||
final void Function() onCancelPressed;
|
||||
const ButtonsDividerBottomDialogWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.onNextPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const Divider(
|
||||
thickness: 0.5,
|
||||
height: 1,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
onTap: onCancelPressed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
alignment: Alignment.center,
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: ColorsManager.grayBorder,
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(26),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cancel',
|
||||
style: TextStyle(color: ColorsManager.blackColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
listener: (context, nonBookableState) {
|
||||
if (nonBookableState is NonBookableSpacesInitial) {
|
||||
context.pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text(
|
||||
'Spaces Added Successfully',
|
||||
style: TextStyle(color: ColorsManager.activeGreen),
|
||||
),
|
||||
duration: Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
),
|
||||
);
|
||||
} else if (nonBookableState is NonBookableSpacesError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
nonBookableState.error,
|
||||
style:
|
||||
const TextStyle(color: ColorsManager.activeGreen),
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, nonBookableState) {
|
||||
return TextButton(
|
||||
onPressed: onNextPressed,
|
||||
child: Text(
|
||||
title,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
class CheckBoxSpaceWidget extends StatelessWidget {
|
||||
final BookableSpacemodel nonBookableSpace;
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
|
||||
const CheckBoxSpaceWidget({
|
||||
super.key,
|
||||
required this.nonBookableSpace,
|
||||
required this.selectedSpaces,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isChecked = selectedSpaces.any(
|
||||
(element) => element.spaceUuid == nonBookableSpace.spaceUuid,
|
||||
);
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: isChecked,
|
||||
onChanged: (value) {
|
||||
final bloc = context.read<NonBookableSpacesBloc>();
|
||||
if (value ?? false) {
|
||||
bloc.add(
|
||||
AddToBookableSpaceEvent(
|
||||
nonBookableSpace: nonBookableSpace,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
bloc.add(
|
||||
RemoveFromBookableSpaceEvent(
|
||||
bookableSpace: nonBookableSpace,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
Expanded(child: Text(nonBookableSpace.spaceName)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ColumnTitleWidget extends StatelessWidget {
|
||||
final bool isFirst;
|
||||
final bool isLast;
|
||||
final String title;
|
||||
const ColumnTitleWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.isFirst,
|
||||
required this.isLast,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.graysColor,
|
||||
borderRadius: isFirst
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
)
|
||||
: isLast
|
||||
? const BorderRadius.only(
|
||||
topRight: Radius.circular(12),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.grayColor,
|
||||
fontSize: 12,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import 'package:data_table_2/data_table_2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CustomDataTable<T> extends StatelessWidget {
|
||||
final List<String> columnsTitles;
|
||||
final List<DataCell> Function(T item) cellsWidgets;
|
||||
final List<T> items;
|
||||
|
||||
const CustomDataTable({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.cellsWidgets,
|
||||
required this.columnsTitles,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DataTable2(
|
||||
dividerThickness: 0.5,
|
||||
columnSpacing: 2,
|
||||
horizontalMargin: 0,
|
||||
empty: SvgPicture.asset(Assets.emptyDataTable),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.circleRolesBackground,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: ColorsManager.textGray,
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
columns: columnsTitles.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final title = entry.value;
|
||||
|
||||
return DataColumn(
|
||||
label: ColumnTitleWidget(
|
||||
title: title,
|
||||
isFirst: index == 0,
|
||||
isLast: index == columnsTitles.length - 1,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
rows: items.map((item) {
|
||||
return DataRow(cells: cellsWidgets(item));
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart';
|
||||
|
||||
class DetailsStepsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
const DetailsStepsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return const SpacesStepDetailsWidget();
|
||||
} else if (state is StepTwoState) {
|
||||
return StepTwoDetailsWidget(
|
||||
pointsController: pointsController,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class BottomPaginationPartWidget extends StatelessWidget {
|
||||
const BottomPaginationPartWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookableSpacesLoaded) {
|
||||
final totalPages = state.bookableSpacesList.totalPages;
|
||||
final currentPage = state.bookableSpacesList.page;
|
||||
|
||||
List<Widget> paginationItems = [];
|
||||
|
||||
// « Two pages back
|
||||
if (currentPage > 2) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '«',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// < One page back
|
||||
if (currentPage > 1) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '<',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage - 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Page numbers
|
||||
for (int i = 1; i <= totalPages; i++) {
|
||||
paginationItems.add(
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (i != currentPage) {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: i == currentPage
|
||||
? ColorsManager.dialogBlueTitle
|
||||
: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'$i',
|
||||
style: TextStyle(
|
||||
color: i == currentPage ? Colors.white : Colors.black,
|
||||
fontWeight: i == currentPage
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// > One page forward
|
||||
if (currentPage < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '>',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// » Two pages forward
|
||||
if (currentPage + 1 < totalPages) {
|
||||
paginationItems.add(
|
||||
_buildArrowButton(
|
||||
label: '»',
|
||||
onTap: () {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: currentPage + 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: paginationItems,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildArrowButton(
|
||||
{required String label, required VoidCallback onTap}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TablePartWidget extends StatelessWidget {
|
||||
const TablePartWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
if (state is BookableSpacesLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (state is BookableSpacesError) {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Text(state.error),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => context
|
||||
.read<BookableSpacesBloc>()
|
||||
.add(LoadBookableSpacesEvent(
|
||||
BookableSpacesParams(currentPage: 1),
|
||||
)),
|
||||
child: const Text('try Again'))
|
||||
]);
|
||||
} else if (state is BookableSpacesLoaded) {
|
||||
return CustomDataTable<BookableSpacemodel>(
|
||||
items: state.bookableSpacesList.data,
|
||||
cellsWidgets: (space) => [
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceName,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
)),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceVirtualAddress,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))),
|
||||
DataCell(Container(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
width: 200,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
children: space.spaceConfig!.bookableDays
|
||||
.map((day) => Text(
|
||||
day,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
)),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig!.bookingStartTime!.format(context),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(
|
||||
Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
space.spaceConfig!.bookingEndTime!.format(context),
|
||||
style: const TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
DataCell(Padding(
|
||||
padding: const EdgeInsetsGeometry.only(left: 10),
|
||||
child: Text(
|
||||
'${space.spaceConfig!.cost} Points',
|
||||
style: const TextStyle(fontSize: 11),
|
||||
))),
|
||||
DataCell(Center(
|
||||
child: Transform.scale(
|
||||
scale: 0.7,
|
||||
child: BlocConsumer<UpdateBookableSpacesBloc,
|
||||
UpdateBookableSpacesState>(
|
||||
listener: (context, updateState) {
|
||||
if (updateState is UpdateBookableSpaceSuccess) {
|
||||
context.read<BookableSpacesBloc>().add(
|
||||
InsertUpdatedSpaceEvent(
|
||||
bookableSpaces: state.bookableSpacesList,
|
||||
bookableSpace: space,
|
||||
updatedBookableSpaceConfig:
|
||||
updateState.bookableSpaceConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (context, updateState) {
|
||||
final isLoading =
|
||||
updateState is UpdateBookableSpaceLoading &&
|
||||
updateState.updatingSpaceUuid == space.spaceUuid;
|
||||
if (isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return Switch(
|
||||
trackOutlineColor:
|
||||
WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
value: space.spaceConfig!.availability,
|
||||
activeTrackColor: ColorsManager.blueColor,
|
||||
inactiveTrackColor: ColorsManager.grayBorder,
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
onChanged: (value) {
|
||||
context.read<UpdateBookableSpacesBloc>().add(
|
||||
UpdateBookableSpace(
|
||||
updatedParams: UpdateBookableSpaceParam(
|
||||
spaceUuid: space.spaceUuid,
|
||||
availability: value,
|
||||
)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)),
|
||||
DataCell(Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: () {},
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
fixedSize: const Size(50, 30),
|
||||
elevation: 1,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.settings,
|
||||
height: 15,
|
||||
color: ColorsManager.blue1,
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
columnsTitles: const [
|
||||
'Space',
|
||||
'Space Virtual Address',
|
||||
'Bookable Days',
|
||||
'Booking Start Time',
|
||||
'Booking End Time',
|
||||
'Cost',
|
||||
'Availability',
|
||||
'Settings',
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TopPartWidget extends StatelessWidget {
|
||||
const TopPartWidget({
|
||||
super.key,
|
||||
required this.pageController,
|
||||
});
|
||||
|
||||
final PageController pageController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(vertical: 5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.backButtonIcon,
|
||||
height: 15,
|
||||
),
|
||||
onPressed: () {
|
||||
pageController.jumpToPage(1);
|
||||
}),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
'Manage Bookable Spaces',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: ColorsManager.vividBlue.withValues(
|
||||
alpha: 0.7,
|
||||
),
|
||||
fontWeight: FontWeight.w700),
|
||||
)
|
||||
],
|
||||
),
|
||||
SvgTextButton(
|
||||
svgSize: 15,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
svgAsset: Assets.addButtonIcon,
|
||||
label: 'Set Up a Bookable Spaces',
|
||||
onPressed: () {
|
||||
final bloc = context.read<BookableSpacesBloc>();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: bloc,
|
||||
child: SetupBookableSpacesDialog(),
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
|
||||
class NextFirstStepButton extends StatelessWidget {
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
|
||||
const NextFirstStepButton({
|
||||
super.key,
|
||||
required this.selectedSpaces,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
title: 'Next',
|
||||
onNextPressed: selectedSpaces.isEmpty
|
||||
? null
|
||||
: () {
|
||||
context.read<StepsCubit>().goToNextStep();
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class PointsPartWidget extends StatelessWidget {
|
||||
const PointsPartWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
});
|
||||
|
||||
final TextEditingController pointsController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (state is ActivatePointsSwitch)
|
||||
Text(
|
||||
'* ',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.copyWith(color: Colors.red),
|
||||
)
|
||||
else
|
||||
const SizedBox(
|
||||
width: 11,
|
||||
),
|
||||
const Text('Points/hrs'),
|
||||
],
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.7,
|
||||
child: Switch(
|
||||
trackOutlineColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
activeTrackColor: ColorsManager.blueColor,
|
||||
inactiveTrackColor: ColorsManager.grayBorder,
|
||||
thumbColor: WidgetStateProperty.resolveWith<Color>(
|
||||
(Set<WidgetState> states) {
|
||||
return ColorsManager.whiteColors;
|
||||
}),
|
||||
value: context.watch<TogglePointsSwitchCubit>().switchValue,
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.activateSwitch();
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = -1,
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
} else {
|
||||
context
|
||||
.read<TogglePointsSwitchCubit>()
|
||||
.unActivateSwitch();
|
||||
pointsController.clear();
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = 0,
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
if (state is ActivatePointsSwitch)
|
||||
SearchUnbookableSpacesWidget(
|
||||
title: 'Ex: 0',
|
||||
height: 40,
|
||||
onChanged: (p0) {
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces
|
||||
.forEach(
|
||||
(e) => e.spaceConfig!.cost = int.parse(
|
||||
pointsController.text.isEmpty
|
||||
? '0'
|
||||
: pointsController.text,
|
||||
),
|
||||
);
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
controller: pointsController,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
suffix: const SizedBox(),
|
||||
)
|
||||
else
|
||||
const SizedBox(),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart';
|
||||
|
||||
class SaveSecondStepButton extends StatelessWidget {
|
||||
final List<BookableSpacemodel> selectedSpaces;
|
||||
final TextEditingController pointsController;
|
||||
|
||||
const SaveSecondStepButton({
|
||||
super.key,
|
||||
required this.selectedSpaces,
|
||||
required this.pointsController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
builder: (context, state) {
|
||||
return ButtonsDividerBottomDialogWidget(
|
||||
title: 'Save',
|
||||
onNextPressed: state is UnValidSaveButtonState
|
||||
? null
|
||||
: () {
|
||||
if (selectedSpaces.any(
|
||||
(element) => element.isValid,
|
||||
)) {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
SendBookableSpacesToApi(),
|
||||
);
|
||||
}
|
||||
},
|
||||
onCancelPressed: () => context.pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class SearchUnbookableSpacesWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget? suffix;
|
||||
final double? height;
|
||||
final double? width;
|
||||
final TextEditingController? controller;
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
final void Function(String)? onChanged;
|
||||
const SearchUnbookableSpacesWidget({
|
||||
required this.title,
|
||||
this.controller,
|
||||
this.onChanged,
|
||||
this.suffix,
|
||||
this.height,
|
||||
this.width,
|
||||
this.inputFormatters,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: width ?? 480,
|
||||
height: height ?? 30,
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x26000000),
|
||||
offset: Offset(0, 4),
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
|
||||
hintText: title,
|
||||
hintStyle: const TextStyle(color: Colors.grey),
|
||||
border: InputBorder.none,
|
||||
suffixIcon:
|
||||
suffix ?? const Icon(Icons.search, size: 20, color: Colors.grey),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/unbookable_list_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpacesStepDetailsWidget extends StatefulWidget {
|
||||
const SpacesStepDetailsWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpacesStepDetailsWidget> createState() =>
|
||||
_SpacesStepDetailsWidgetState();
|
||||
}
|
||||
|
||||
class _SpacesStepDetailsWidgetState extends State<SpacesStepDetailsWidget> {
|
||||
Timer? _debounce;
|
||||
ScrollController scrollController = ScrollController();
|
||||
int currentPage = 1;
|
||||
String? currentSearchTerm;
|
||||
bool isLoadingMore = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
scrollController.addListener(() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 100) {
|
||||
final state = context.read<NonBookableSpacesBloc>().state;
|
||||
if (state is NonBookableSpacesLoaded &&
|
||||
state.nonBookableSpaces.hasNext &&
|
||||
!isLoadingMore) {
|
||||
isLoadingMore = true;
|
||||
currentPage++;
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams: NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Select Space',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Container(
|
||||
width: 450,
|
||||
height: 480,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x40000000),
|
||||
offset: Offset.zero,
|
||||
blurRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 520,
|
||||
height: 70,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF8F8F8),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: SearchUnbookableSpacesWidget(
|
||||
title: 'Search',
|
||||
onChanged: (p0) {
|
||||
if (_debounce?.isActive ?? false) _debounce!.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 500), () {
|
||||
currentSearchTerm = p0;
|
||||
currentPage = 1;
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams: NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child:
|
||||
BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
|
||||
listener: (context, state) {
|
||||
if (state is NonBookableSpacesLoaded) {
|
||||
isLoadingMore = false;
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state is NonBookableSpacesError) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(state.error),
|
||||
const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
context.read<NonBookableSpacesBloc>().add(
|
||||
LoadUnBookableSpacesEvent(
|
||||
nonBookableSpacesParams:
|
||||
NonBookableSpacesParams(
|
||||
currentPage: currentPage,
|
||||
searchedWords: currentSearchTerm,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Try Again'))
|
||||
],
|
||||
);
|
||||
} else if (state is NonBookableSpacesLoading) {
|
||||
if (state.lastNonBookableSpaces == null) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
} else {
|
||||
return UnbookableListWidget(
|
||||
scrollController: scrollController,
|
||||
nonBookableSpaces: state.lastNonBookableSpaces!,
|
||||
);
|
||||
}
|
||||
} else if (state is NonBookableSpacesLoaded) {
|
||||
return UnbookableListWidget(
|
||||
scrollController: scrollController,
|
||||
nonBookableSpaces: state.nonBookableSpaces,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/booking_period_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart';
|
||||
|
||||
class StepTwoDetailsWidget extends StatelessWidget {
|
||||
final TextEditingController pointsController;
|
||||
const StepTwoDetailsWidget({
|
||||
super.key,
|
||||
required this.pointsController,
|
||||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 450,
|
||||
height: 480,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const WeekDaysCheckboxRow(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const BookingPeriodWidget(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => TogglePointsSwitchCubit()..activateSwitch(),
|
||||
child: PointsPartWidget(pointsController: pointsController),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class StepperPartWidget extends StatelessWidget {
|
||||
const StepperPartWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsetsGeometry.only(left: 20),
|
||||
child: BlocBuilder<StepsCubit, StepsState>(
|
||||
builder: (context, state) {
|
||||
if (state is StepOneState) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
titleColor: ColorsManager.softGray,
|
||||
circleColor: ColorsManager.whiteColors,
|
||||
borderColor: ColorsManager.textGray,
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (state is StepTwoState) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Space',
|
||||
titleColor: ColorsManager.softGray,
|
||||
cicleIcon: Icon(
|
||||
Icons.check,
|
||||
color: ColorsManager.whiteColors,
|
||||
size: 12,
|
||||
),
|
||||
circleColor: ColorsManager.trueIconGreen,
|
||||
radius: 15,
|
||||
borderColor: ColorsManager.trueIconGreen,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.only(left: 3),
|
||||
alignment: Alignment.centerLeft,
|
||||
height: 50,
|
||||
child: const VerticalDivider(
|
||||
width: 8,
|
||||
)),
|
||||
const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
)
|
||||
],
|
||||
);
|
||||
} else if (state is StepEditMode) {
|
||||
return const CircleTitleStepperWidget(
|
||||
title: 'Settings',
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CircleTitleStepperWidget extends StatelessWidget {
|
||||
final double? radius;
|
||||
final Widget? cicleIcon;
|
||||
final Color? circleColor;
|
||||
final Color? borderColor;
|
||||
final Color? titleColor;
|
||||
final String title;
|
||||
const CircleTitleStepperWidget({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.circleColor,
|
||||
this.borderColor,
|
||||
this.cicleIcon,
|
||||
this.titleColor,
|
||||
this.radius,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: radius ?? 15,
|
||||
height: radius ?? 15,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: circleColor ?? ColorsManager.blue1,
|
||||
border: Border.all(color: borderColor ?? ColorsManager.blue1)),
|
||||
child: cicleIcon,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: titleColor ?? ColorsManager.blackColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class TimePickerWidget extends StatefulWidget {
|
||||
final String title;
|
||||
const TimePickerWidget({
|
||||
super.key,
|
||||
required this.onTimePicked,
|
||||
required this.title,
|
||||
});
|
||||
|
||||
final void Function(TimeOfDay? timePicked) onTimePicked;
|
||||
@override
|
||||
State<TimePickerWidget> createState() => _TimePickerWidgetState();
|
||||
}
|
||||
|
||||
class _TimePickerWidgetState extends State<TimePickerWidget> {
|
||||
TimeOfDay? timePicked;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
onTap: () async {
|
||||
final tempTime = await showTimePicker(
|
||||
context: context,
|
||||
initialTime: TimeOfDay.now(),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: ColorsManager.primaryColor,
|
||||
onSurface: Colors.black,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
widget.onTimePicked(tempTime);
|
||||
timePicked = tempTime;
|
||||
context.read<NonBookableSpacesBloc>().add(CheckConfigurValidityEvent());
|
||||
setState(() {});
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 32,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
timePicked == null ? widget.title : timePicked!.format(context),
|
||||
style: TextStyle(
|
||||
color: ColorsManager.blackColor.withValues(alpha: 0.4),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/check_box_space_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
|
||||
|
||||
class UnbookableListWidget extends StatelessWidget {
|
||||
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
|
||||
const UnbookableListWidget({
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
required this.nonBookableSpaces,
|
||||
});
|
||||
|
||||
final ScrollController scrollController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 490,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
bottom: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 10, left: 20, bottom: 5),
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) => const SizedBox(
|
||||
height: 5,
|
||||
),
|
||||
controller: scrollController,
|
||||
itemCount: nonBookableSpaces.data.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < nonBookableSpaces.data.length) {
|
||||
return CheckBoxSpaceWidget(
|
||||
nonBookableSpace: nonBookableSpaces.data[index],
|
||||
selectedSpaces:
|
||||
context.read<NonBookableSpacesBloc>().selectedBookableSpaces,
|
||||
);
|
||||
} else {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 10),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
|
||||
|
||||
class WeekDaysCheckboxRow extends StatefulWidget {
|
||||
const WeekDaysCheckboxRow({super.key});
|
||||
|
||||
@override
|
||||
State<WeekDaysCheckboxRow> createState() => _WeekDaysCheckboxRowState();
|
||||
}
|
||||
|
||||
class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
|
||||
final Map<String, bool> _daysChecked = {
|
||||
'Mon': false,
|
||||
'Tue': false,
|
||||
'Wed': false,
|
||||
'Thu': false,
|
||||
'Fri': false,
|
||||
'Sat': false,
|
||||
'Sun': false,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _daysChecked.entries.map((entry) {
|
||||
return Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Checkbox(
|
||||
value: entry.value,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_daysChecked[entry.key] = newValue ?? false;
|
||||
final selectedDays = _daysChecked.entries
|
||||
.where((e) => e.value)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
|
||||
for (var space in context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.selectedBookableSpaces) {
|
||||
space.spaceConfig!.bookableDays = selectedDays;
|
||||
}
|
||||
});
|
||||
|
||||
context
|
||||
.read<NonBookableSpacesBloc>()
|
||||
.add(CheckConfigurValidityEvent());
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_bloc.dart';
|
||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||
import 'package:syncrow_web/pages/access_management/booking_system/view/booking_page.dart';
|
||||
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart';
|
||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
@ -72,14 +71,9 @@ class _AccessManagementPageState extends State<AccessManagementPage>
|
||||
scaffoldBody: PageView(
|
||||
controller: _pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
const AccessOverviewContent(),
|
||||
BookingPage(
|
||||
pageController: _pageController,
|
||||
),
|
||||
ManageBookableSpacesPage(
|
||||
pageController: _pageController,
|
||||
),
|
||||
children: const [
|
||||
AccessOverviewContent(),
|
||||
BookingPage(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -105,7 +105,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
color: const Color(0xFF0026A2),
|
||||
),
|
||||
HomeItemModel(
|
||||
title: 'Devices Management',
|
||||
title: 'Device Management',
|
||||
icon: Assets.devicesIcon,
|
||||
active: true,
|
||||
onPress: (context) {
|
||||
|
@ -1,24 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_community/presentation/edit_community_dialog.dart';
|
||||
|
||||
abstract final class SpaceManagementCommunityDialogHelper {
|
||||
static void showCreateDialog(BuildContext context) {
|
||||
static void showCreateDialog(BuildContext context) => showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => const CreateCommunityDialog(),
|
||||
);
|
||||
|
||||
static void showEditDialog(
|
||||
BuildContext context,
|
||||
CommunityModel community,
|
||||
) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => CreateCommunityDialog(
|
||||
title: const SelectableText('Community Name'),
|
||||
onCreateCommunity: (community) {
|
||||
context.read<CommunitiesBloc>().add(
|
||||
InsertCommunity(community),
|
||||
);
|
||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||
SelectCommunityEvent(community: community),
|
||||
);
|
||||
},
|
||||
builder: (_) => EditCommunityDialog(
|
||||
community: community,
|
||||
parentContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void showLoadingDialog(BuildContext context) => showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
|
||||
static void showSuccessSnackBar(BuildContext context, String message) =>
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -40,22 +40,4 @@ class PaginatedDataModel<T> extends Equatable {
|
||||
totalItems,
|
||||
totalPages,
|
||||
];
|
||||
|
||||
PaginatedDataModel<T> copyWith({
|
||||
List<T>? data,
|
||||
int? page,
|
||||
int? size,
|
||||
bool? hasNext,
|
||||
int? totalItems,
|
||||
int? totalPages,
|
||||
}) {
|
||||
return PaginatedDataModel<T>(
|
||||
data: data ?? this.data,
|
||||
page: page ?? this.page,
|
||||
size: size ?? this.size,
|
||||
hasNext: hasNext ?? this.hasNext,
|
||||
totalItems: totalItems ?? this.totalItems,
|
||||
totalPages: totalPages ?? this.totalPages,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_name_text_field.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class CreateCommunityDialogWidget extends StatefulWidget {
|
||||
class CommunityDialog extends StatefulWidget {
|
||||
final String? initialName;
|
||||
final Widget title;
|
||||
final void Function(String name) onSubmit;
|
||||
final String? errorMessage;
|
||||
|
||||
const CreateCommunityDialogWidget({
|
||||
super.key,
|
||||
const CommunityDialog({
|
||||
required this.title,
|
||||
required this.onSubmit,
|
||||
this.initialName,
|
||||
this.errorMessage,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateCommunityDialogWidget> createState() =>
|
||||
_CreateCommunityDialogWidgetState();
|
||||
State<CommunityDialog> createState() => _CommunityDialogState();
|
||||
}
|
||||
|
||||
class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidget> {
|
||||
class _CommunityDialogState extends State<CommunityDialog> {
|
||||
late final TextEditingController _nameController;
|
||||
|
||||
@override
|
||||
@ -63,35 +64,20 @@ class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidge
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: SingleChildScrollView(
|
||||
child: BlocBuilder<CreateCommunityBloc, CreateCommunityState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.headlineMedium!,
|
||||
child: widget.title,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
CreateCommunityNameTextField(
|
||||
nameController: _nameController,
|
||||
),
|
||||
if (state case CreateCommunityFailure(:final message))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 18),
|
||||
child: SelectableText(
|
||||
'* $message',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
_buildActionButtons(context),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.headlineMedium!,
|
||||
child: widget.title,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
CreateCommunityNameTextField(nameController: _nameController),
|
||||
_buildErrorMessage(),
|
||||
const SizedBox(height: 24),
|
||||
_buildActionButtons(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -132,13 +118,22 @@ class _CreateCommunityDialogWidgetState extends State<CreateCommunityDialogWidge
|
||||
|
||||
void _onSubmit(BuildContext context) {
|
||||
if (_formKey.currentState?.validate() ?? false) {
|
||||
context.read<CreateCommunityBloc>().add(
|
||||
CreateCommunity(
|
||||
CreateCommunityParam(
|
||||
name: _nameController.text.trim(),
|
||||
),
|
||||
),
|
||||
);
|
||||
widget.onSubmit.call(_nameController.text.trim());
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildErrorMessage() {
|
||||
return Visibility(
|
||||
visible: widget.errorMessage != null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.symmetric(vertical: 18),
|
||||
child: SelectableText(
|
||||
'* ${widget.errorMessage}',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,9 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/s
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_subspaces_decorator.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
|
||||
import 'package:syncrow_web/web_layout/web_scaffold.dart';
|
||||
@ -26,6 +29,13 @@ class SpaceManagementPage extends StatelessWidget {
|
||||
)..add(const LoadCommunities(LoadCommunitiesParam())),
|
||||
),
|
||||
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
|
||||
BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
UniqueSubspacesDecorator(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: WebScaffold(
|
||||
appBarTitle: Text(
|
||||
|
@ -0,0 +1,109 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CommunityStructureHeader extends StatelessWidget {
|
||||
const CommunityStructureHeader({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.whiteColors,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: ColorsManager.shadowBlackColor,
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildCommunityInfo(context, theme, screenWidth),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCommunityInfo(
|
||||
BuildContext context, ThemeData theme, double screenWidth) {
|
||||
final selectedCommunity =
|
||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedCommunity;
|
||||
final selectedSpace =
|
||||
context.watch<CommunitiesTreeSelectionBloc>().state.selectedSpace;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Community Structure',
|
||||
style: theme.textTheme.headlineLarge
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
),
|
||||
if (selectedCommunity != null)
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: SelectableText(
|
||||
selectedCommunity.name,
|
||||
style: theme.textTheme.bodyLarge
|
||||
?.copyWith(color: ColorsManager.blackColor),
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
SpaceManagementCommunityDialogHelper.showEditDialog(
|
||||
context,
|
||||
selectedCommunity,
|
||||
);
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.iconEdit,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CommunityStructureHeaderActionButtons(
|
||||
onDelete: (space) {},
|
||||
onDuplicate: (space) {},
|
||||
onEdit: (space) {
|
||||
SpaceDetailsDialogHelper.showEdit(
|
||||
context,
|
||||
spaceModel: selectedSpace!,
|
||||
);
|
||||
},
|
||||
selectedSpace: selectedSpace,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header_button.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class CommunityStructureHeaderActionButtons extends StatelessWidget {
|
||||
const CommunityStructureHeaderActionButtons({
|
||||
super.key,
|
||||
required this.onDelete,
|
||||
required this.selectedSpace,
|
||||
required this.onDuplicate,
|
||||
required this.onEdit,
|
||||
});
|
||||
|
||||
final void Function(SpaceModel space) onDelete;
|
||||
final void Function(SpaceModel space) onDuplicate;
|
||||
final void Function(SpaceModel space) onEdit;
|
||||
final SpaceModel? selectedSpace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
alignment: WrapAlignment.end,
|
||||
spacing: 10,
|
||||
children: [
|
||||
if (selectedSpace != null) ...[
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Edit',
|
||||
svgAsset: Assets.editSpace,
|
||||
onPressed: () => onEdit(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Duplicate',
|
||||
svgAsset: Assets.duplicate,
|
||||
onPressed: () => onDuplicate(selectedSpace!),
|
||||
),
|
||||
CommunityStructureHeaderButton(
|
||||
label: 'Delete',
|
||||
svgAsset: Assets.spaceDelete,
|
||||
onPressed: () => onDelete(selectedSpace!),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class CommunityStructureHeaderButton extends StatelessWidget {
|
||||
const CommunityStructureHeaderButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.onPressed,
|
||||
this.svgAsset,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final VoidCallback onPressed;
|
||||
final String? svgAsset;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double buttonHeight = 40;
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 130,
|
||||
minHeight: buttonHeight,
|
||||
),
|
||||
child: DefaultButton(
|
||||
onPressed: onPressed,
|
||||
borderWidth: 2,
|
||||
backgroundColor: ColorsManager.textFieldGreyColor,
|
||||
foregroundColor: ColorsManager.blackColor,
|
||||
borderRadius: 12.0,
|
||||
padding: 2.0,
|
||||
height: buttonHeight,
|
||||
elevation: 0,
|
||||
borderColor: ColorsManager.lightGrayColor,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (svgAsset != null)
|
||||
SvgPicture.asset(
|
||||
svgAsset!,
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: context.textTheme.bodySmall
|
||||
?.copyWith(color: ColorsManager.blackColor, fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_canvas.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/community_structure_header.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/create_space_button.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
|
||||
@ -18,9 +19,17 @@ class SpaceManagementCommunityStructure extends StatelessWidget {
|
||||
replacement: const Row(
|
||||
children: [spacer, Expanded(child: CreateSpaceButton()), spacer],
|
||||
),
|
||||
child: CommunityStructureCanvas(
|
||||
community: selectedCommunity,
|
||||
selectedSpace: selectedSpace,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CommunityStructureHeader(),
|
||||
Expanded(
|
||||
child: CommunityStructureCanvas(
|
||||
community: selectedCommunity,
|
||||
selectedSpace: selectedSpace,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -39,6 +39,26 @@ class CommunityModel extends Equatable {
|
||||
.toList();
|
||||
}
|
||||
|
||||
CommunityModel copyWith({
|
||||
String? uuid,
|
||||
String? name,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? description,
|
||||
String? externalId,
|
||||
List<SpaceModel>? spaces,
|
||||
}) {
|
||||
return CommunityModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
name: name ?? this.name,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
description: description ?? this.description,
|
||||
externalId: externalId ?? this.externalId,
|
||||
spaces: spaces ?? this.spaces,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uuid, name, spaces];
|
||||
}
|
||||
|
@ -19,6 +19,16 @@ class SpaceModel extends Equatable {
|
||||
required this.parent,
|
||||
});
|
||||
|
||||
factory SpaceModel.empty() => const SpaceModel(
|
||||
uuid: '',
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
spaceName: '',
|
||||
icon: '',
|
||||
children: [],
|
||||
parent: null,
|
||||
);
|
||||
|
||||
factory SpaceModel.fromJson(Map<String, dynamic> json) {
|
||||
return SpaceModel(
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
|
@ -16,6 +16,7 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
|
||||
on<LoadCommunities>(_onLoadCommunities);
|
||||
on<LoadMoreCommunities>(_onLoadMoreCommunities);
|
||||
on<InsertCommunity>(_onInsertCommunity);
|
||||
on<CommunitiesUpdateCommunity>(_onCommunitiesUpdateCommunity);
|
||||
}
|
||||
|
||||
final CommunitiesService _communitiesService;
|
||||
@ -114,4 +115,18 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
|
||||
) {
|
||||
emit(state.copyWith(communities: [event.community, ...state.communities]));
|
||||
}
|
||||
|
||||
void _onCommunitiesUpdateCommunity(
|
||||
CommunitiesUpdateCommunity event,
|
||||
Emitter<CommunitiesState> emit,
|
||||
) {
|
||||
final updatedCommunities = state.communities
|
||||
.map((e) => e.uuid == event.community.uuid ? event.community : e)
|
||||
.toList();
|
||||
emit(
|
||||
state.copyWith(
|
||||
communities: updatedCommunities,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -31,3 +31,12 @@ final class InsertCommunity extends CommunitiesEvent {
|
||||
@override
|
||||
List<Object?> get props => [community];
|
||||
}
|
||||
|
||||
final class CommunitiesUpdateCommunity extends CommunitiesEvent {
|
||||
const CommunitiesUpdateCommunity(this.community);
|
||||
|
||||
final CommunityModel community;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [community];
|
||||
}
|
||||
|
@ -1,57 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/helpers/space_management_community_dialog_helper.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/widgets/community_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/data/services/remote_create_community_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/domain/param/create_community_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/bloc/create_community_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/create_community/presentation/create_community_dialog_widget.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class CreateCommunityDialog extends StatelessWidget {
|
||||
final void Function(CommunityModel community) onCreateCommunity;
|
||||
final String? initialName;
|
||||
final Widget title;
|
||||
|
||||
const CreateCommunityDialog({
|
||||
super.key,
|
||||
required this.onCreateCommunity,
|
||||
required this.title,
|
||||
this.initialName,
|
||||
});
|
||||
const CreateCommunityDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => CreateCommunityBloc(RemoteCreateCommunityService(HTTPService())),
|
||||
child: BlocListener<CreateCommunityBloc, CreateCommunityState>(
|
||||
create: (_) => CreateCommunityBloc(
|
||||
RemoteCreateCommunityService(HTTPService()),
|
||||
),
|
||||
child: BlocConsumer<CreateCommunityBloc, CreateCommunityState>(
|
||||
listener: (context, state) {
|
||||
switch (state) {
|
||||
case CreateCommunityLoading():
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
case CreateCommunityLoading() || CreateCommunityInitial():
|
||||
SpaceManagementCommunityDialogHelper.showLoadingDialog(context);
|
||||
break;
|
||||
case CreateCommunitySuccess(:final community):
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Community created successfully')),
|
||||
SpaceManagementCommunityDialogHelper.showSuccessSnackBar(
|
||||
context,
|
||||
'${community.name} community created successfully',
|
||||
);
|
||||
onCreateCommunity.call(community);
|
||||
context.read<CommunitiesBloc>().add(
|
||||
InsertCommunity(community),
|
||||
);
|
||||
context.read<CommunitiesTreeSelectionBloc>().add(
|
||||
SelectCommunityEvent(community: community),
|
||||
);
|
||||
break;
|
||||
case CreateCommunityFailure():
|
||||
Navigator.of(context).pop();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
child: CreateCommunityDialogWidget(
|
||||
title: title,
|
||||
initialName: initialName,
|
||||
),
|
||||
builder: (BuildContext context, CreateCommunityState state) {
|
||||
return CommunityDialog(
|
||||
title: const Text('Create Community'),
|
||||
initialName: null,
|
||||
onSubmit: (name) => context.read<CreateCommunityBloc>().add(
|
||||
CreateCommunity(CreateCommunityParam(name: name)),
|
||||
),
|
||||
errorMessage: state is CreateCommunityFailure ? state.message : null,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
@ -15,12 +16,15 @@ class RemoteSpaceDetailsService implements SpaceDetailsService {
|
||||
static const _defaultErrorMessage = 'Failed to load space details';
|
||||
|
||||
@override
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpacesParam param) async {
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
path: await _makeEndpoint(param),
|
||||
expectedResponseModel: (data) {
|
||||
return SpaceDetailsModel.fromJson(data as Map<String, dynamic>);
|
||||
final response = data as Map<String, dynamic>;
|
||||
return SpaceDetailsModel.fromJson(
|
||||
response['data'] as Map<String, dynamic>,
|
||||
);
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@ -37,4 +41,13 @@ class RemoteSpaceDetailsService implements SpaceDetailsService {
|
||||
throw APIException(formattedErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> _makeEndpoint(LoadSpaceDetailsParam param) async {
|
||||
final projectUuid = await ProjectManager.getProjectUUID();
|
||||
if (projectUuid == null || projectUuid.isEmpty) {
|
||||
throw APIException('Project UUID is not set');
|
||||
}
|
||||
|
||||
return '/projects/$projectUuid/communities/${param.communityUuid}/spaces/${param.spaceUuid}';
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||
|
||||
class UniqueSubspacesDecorator implements SpaceDetailsService {
|
||||
final SpaceDetailsService _decoratee;
|
||||
|
||||
const UniqueSubspacesDecorator(this._decoratee);
|
||||
|
||||
@override
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param) async {
|
||||
final response = await _decoratee.getSpaceDetails(param);
|
||||
|
||||
final uniqueSubspaces = <String, Subspace>{};
|
||||
|
||||
for (final subspace in response.subspaces) {
|
||||
final normalizedName = subspace.name.trim().toLowerCase();
|
||||
if (!uniqueSubspaces.containsKey(normalizedName)) {
|
||||
uniqueSubspaces[normalizedName] = subspace;
|
||||
}
|
||||
}
|
||||
|
||||
return response.copyWith(
|
||||
subspaces: uniqueSubspaces.values.toList(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/products/domain/models/product.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/tags/domain/models/tag.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class SpaceDetailsModel extends Equatable {
|
||||
final String uuid;
|
||||
@ -17,14 +18,21 @@ class SpaceDetailsModel extends Equatable {
|
||||
required this.subspaces,
|
||||
});
|
||||
|
||||
factory SpaceDetailsModel.empty() => const SpaceDetailsModel(
|
||||
uuid: '',
|
||||
spaceName: '',
|
||||
icon: Assets.villa,
|
||||
productAllocations: [],
|
||||
subspaces: [],
|
||||
);
|
||||
factory SpaceDetailsModel.fromJson(Map<String, dynamic> json) {
|
||||
return SpaceDetailsModel(
|
||||
uuid: json['uuid'] as String,
|
||||
spaceName: json['spaceName'] as String,
|
||||
icon: json['icon'] as String,
|
||||
productAllocations: (json['productAllocations'] as List)
|
||||
.map((e) => ProductAllocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
.map((e) => ProductAllocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
subspaces: (json['subspaces'] as List)
|
||||
.map((e) => Subspace.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
@ -41,6 +49,22 @@ class SpaceDetailsModel extends Equatable {
|
||||
};
|
||||
}
|
||||
|
||||
SpaceDetailsModel copyWith({
|
||||
String? uuid,
|
||||
String? spaceName,
|
||||
String? icon,
|
||||
List<ProductAllocation>? productAllocations,
|
||||
List<Subspace>? subspaces,
|
||||
}) {
|
||||
return SpaceDetailsModel(
|
||||
uuid: uuid ?? this.uuid,
|
||||
spaceName: spaceName ?? this.spaceName,
|
||||
icon: icon ?? this.icon,
|
||||
productAllocations: productAllocations ?? this.productAllocations,
|
||||
subspaces: subspaces ?? this.subspaces,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uuid, spaceName, icon, productAllocations, subspaces];
|
||||
}
|
||||
@ -48,12 +72,10 @@ class SpaceDetailsModel extends Equatable {
|
||||
class ProductAllocation extends Equatable {
|
||||
final Product product;
|
||||
final Tag tag;
|
||||
final String? location;
|
||||
|
||||
const ProductAllocation({
|
||||
required this.product,
|
||||
required this.tag,
|
||||
this.location,
|
||||
});
|
||||
|
||||
factory ProductAllocation.fromJson(Map<String, dynamic> json) {
|
||||
@ -70,6 +92,16 @@ class ProductAllocation extends Equatable {
|
||||
};
|
||||
}
|
||||
|
||||
ProductAllocation copyWith({
|
||||
Product? product,
|
||||
Tag? tag,
|
||||
}) {
|
||||
return ProductAllocation(
|
||||
product: product ?? this.product,
|
||||
tag: tag ?? this.tag,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [product, tag];
|
||||
}
|
||||
@ -88,7 +120,7 @@ class Subspace extends Equatable {
|
||||
factory Subspace.fromJson(Map<String, dynamic> json) {
|
||||
return Subspace(
|
||||
uuid: json['uuid'] as String,
|
||||
name: json['name'] as String,
|
||||
name: json['subspaceName'] as String,
|
||||
productAllocations: (json['productAllocations'] as List)
|
||||
.map((e) => ProductAllocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
@ -103,6 +135,18 @@ class Subspace extends Equatable {
|
||||
};
|
||||
}
|
||||
|
||||
Subspace copyWith({
|
||||
String? uuid,
|
||||
String? name,
|
||||
List<ProductAllocation>? productAllocations,
|
||||
}) {
|
||||
return Subspace(
|
||||
uuid: uuid ?? this.uuid,
|
||||
name: name ?? this.name,
|
||||
productAllocations: productAllocations ?? this.productAllocations,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [uuid, name, productAllocations];
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
class LoadSpaceDetailsParam {
|
||||
const LoadSpaceDetailsParam({
|
||||
required this.spaceUuid,
|
||||
required this.communityUuid,
|
||||
});
|
||||
|
||||
final String spaceUuid;
|
||||
final String communityUuid;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
class LoadSpacesParam {
|
||||
const LoadSpacesParam();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
|
||||
abstract class SpaceDetailsService {
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpacesParam param);
|
||||
Future<SpaceDetailsModel> getSpaceDetails(LoadSpaceDetailsParam param);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_spaces_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/services/space_details_service.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
|
||||
@ -9,12 +9,13 @@ part 'space_details_event.dart';
|
||||
part 'space_details_state.dart';
|
||||
|
||||
class SpaceDetailsBloc extends Bloc<SpaceDetailsEvent, SpaceDetailsState> {
|
||||
final SpaceDetailsService _spaceDetailsService;
|
||||
|
||||
SpaceDetailsBloc(this._spaceDetailsService) : super(SpaceDetailsInitial()) {
|
||||
on<LoadSpaceDetails>(_onLoadSpaceDetails);
|
||||
on<ClearSpaceDetails>(_onClearSpaceDetails);
|
||||
}
|
||||
|
||||
final SpaceDetailsService _spaceDetailsService;
|
||||
|
||||
Future<void> _onLoadSpaceDetails(
|
||||
LoadSpaceDetails event,
|
||||
Emitter<SpaceDetailsState> emit,
|
||||
@ -31,4 +32,11 @@ class SpaceDetailsBloc extends Bloc<SpaceDetailsEvent, SpaceDetailsState> {
|
||||
emit(SpaceDetailsFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _onClearSpaceDetails(
|
||||
ClearSpaceDetails event,
|
||||
Emitter<SpaceDetailsState> emit,
|
||||
) {
|
||||
emit(SpaceDetailsInitial());
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,18 @@ sealed class SpaceDetailsEvent extends Equatable {
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class LoadSpaceDetails extends SpaceDetailsEvent {
|
||||
final class LoadSpaceDetails extends SpaceDetailsEvent {
|
||||
const LoadSpaceDetails(this.param);
|
||||
|
||||
final LoadSpacesParam param;
|
||||
final LoadSpaceDetailsParam param;
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
||||
|
||||
final class ClearSpaceDetails extends SpaceDetailsEvent {
|
||||
const ClearSpaceDetails();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
@ -21,10 +21,10 @@ final class SpaceDetailsLoaded extends SpaceDetailsState {
|
||||
}
|
||||
|
||||
final class SpaceDetailsFailure extends SpaceDetailsState {
|
||||
final String message;
|
||||
final String errorMessage;
|
||||
|
||||
const SpaceDetailsFailure(this.message);
|
||||
const SpaceDetailsFailure(this.errorMessage);
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
List<Object> get props => [errorMessage];
|
||||
}
|
||||
|
@ -1,11 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_dialog.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
abstract final class SpaceDetailsDialogHelper {
|
||||
static void showCreate(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const SpaceDetailsDialog(),
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
),
|
||||
child: SpaceDetailsDialog(
|
||||
context: context,
|
||||
title: const SelectableText('Create Space'),
|
||||
spaceModel: SpaceModel.empty(),
|
||||
onSave: print,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void showEdit(
|
||||
BuildContext context, {
|
||||
required SpaceModel spaceModel,
|
||||
}) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => SpaceDetailsBloc(
|
||||
RemoteSpaceDetailsService(httpService: HTTPService()),
|
||||
),
|
||||
child: SpaceDetailsDialog(
|
||||
context: context,
|
||||
title: const SelectableText('Edit Space'),
|
||||
spaceModel: spaceModel,
|
||||
onSave: (space) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class ButtonContentWidget extends StatelessWidget {
|
||||
final String label;
|
||||
final String? svgAssets;
|
||||
final bool disabled;
|
||||
|
||||
const ButtonContentWidget({
|
||||
required this.label,
|
||||
this.svgAssets,
|
||||
this.disabled = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Opacity(
|
||||
opacity: disabled ? 0.5 : 1.0,
|
||||
child: Container(
|
||||
width: screenWidth * 0.25,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
border: Border.all(
|
||||
color: ColorsManager.neutralGray,
|
||||
width: 3.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (svgAssets != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 6.0),
|
||||
child: SvgPicture.asset(
|
||||
svgAssets!,
|
||||
width: screenWidth * 0.015,
|
||||
height: screenWidth * 0.015,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/cancel_button.dart';
|
||||
import 'package:syncrow_web/pages/common/buttons/default_button.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class SpaceDetailsActionButtons extends StatelessWidget {
|
||||
const SpaceDetailsActionButtons({
|
||||
super.key,
|
||||
required this.onSave,
|
||||
required this.onCancel,
|
||||
});
|
||||
|
||||
final VoidCallback onCancel;
|
||||
final VoidCallback? onSave;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 10,
|
||||
children: [
|
||||
Expanded(child: _buildCancelButton(context)),
|
||||
Expanded(child: _buildSaveButton()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCancelButton(BuildContext context) {
|
||||
return CancelButton(
|
||||
onPressed: onCancel,
|
||||
label: 'Cancel',
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSaveButton() {
|
||||
return DefaultButton(
|
||||
onPressed: onSave,
|
||||
borderRadius: 10,
|
||||
backgroundColor: ColorsManager.secondaryColor,
|
||||
foregroundColor: ColorsManager.whiteColors,
|
||||
child: const Text('OK'),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class SpaceDetailsDevicesBox extends StatelessWidget {
|
||||
const SpaceDetailsDevicesBox({
|
||||
required this.space,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final SpaceDetailsModel space;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final productAllocations = space.productAllocations;
|
||||
final subspaces = space.subspaces;
|
||||
final isAnySubspaceHasProductAllocations =
|
||||
subspaces.any((subspace) => subspace.productAllocations.isNotEmpty);
|
||||
if (productAllocations.isNotEmpty || isAnySubspaceHasProductAllocations) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
// Combine tags from spaceModel and subspaces
|
||||
// ...TagHelper.groupTags([
|
||||
// ...?tags,
|
||||
// ...?subspaces?.expand((subspace) => subspace.tags ?? [])
|
||||
// ]).entries.map(
|
||||
// (entry) => Chip(
|
||||
// avatar: SizedBox(
|
||||
// width: 24,
|
||||
// height: 24,
|
||||
// child: SvgPicture.asset(
|
||||
// entry.key.icon ?? 'assets/icons/gateway.svg',
|
||||
// fit: BoxFit.contain,
|
||||
// ),
|
||||
// ),
|
||||
// label: Text(
|
||||
// 'x${entry.value}',
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodySmall
|
||||
// ?.copyWith(color: ColorsManager.spaceColor),
|
||||
// ),
|
||||
// backgroundColor: ColorsManager.whiteColors,
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(16),
|
||||
// side: const BorderSide(
|
||||
// color: ColorsManager.spaceColor,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
EditChip(
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return TextButton(
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
child: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: ButtonContentWidget(
|
||||
svgAssets: Assets.addIcon,
|
||||
label: 'Add Devices',
|
||||
// disabled: isTagsAndSubspaceModelDisabled,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/params/load_space_details_param.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_form.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceDetailsDialog extends StatelessWidget {
|
||||
const SpaceDetailsDialog({super.key});
|
||||
class SpaceDetailsDialog extends StatefulWidget {
|
||||
const SpaceDetailsDialog({
|
||||
required this.title,
|
||||
required this.spaceModel,
|
||||
required this.onSave,
|
||||
required this.context,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget title;
|
||||
final SpaceModel spaceModel;
|
||||
final void Function(SpaceDetailsModel space) onSave;
|
||||
final BuildContext context;
|
||||
|
||||
@override
|
||||
State<SpaceDetailsDialog> createState() => _SpaceDetailsDialogState();
|
||||
}
|
||||
|
||||
class _SpaceDetailsDialogState extends State<SpaceDetailsDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
final isCreateMode = widget.spaceModel.uuid.isEmpty;
|
||||
|
||||
if (!isCreateMode) {
|
||||
final param = LoadSpaceDetailsParam(
|
||||
spaceUuid: widget.spaceModel.uuid,
|
||||
communityUuid: widget.context
|
||||
.read<CommunitiesTreeSelectionBloc>()
|
||||
.state
|
||||
.selectedCommunity!
|
||||
.uuid,
|
||||
);
|
||||
widget.context.read<SpaceDetailsBloc>().add(LoadSpaceDetails(param));
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Dialog(
|
||||
child: Text('Create Space'),
|
||||
final isCreateMode = widget.spaceModel.uuid.isEmpty;
|
||||
if (isCreateMode) {
|
||||
return SpaceDetailsForm(
|
||||
title: widget.title,
|
||||
space: SpaceDetailsModel.empty(),
|
||||
onSave: widget.onSave,
|
||||
);
|
||||
}
|
||||
|
||||
return BlocBuilder<SpaceDetailsBloc, SpaceDetailsState>(
|
||||
bloc: widget.context.read<SpaceDetailsBloc>(),
|
||||
builder: (context, state) => switch (state) {
|
||||
SpaceDetailsInitial() => _buildLoadingDialog(),
|
||||
SpaceDetailsLoading() => _buildLoadingDialog(),
|
||||
SpaceDetailsLoaded(:final spaceDetails) => SpaceDetailsForm(
|
||||
title: widget.title,
|
||||
space: spaceDetails,
|
||||
onSave: widget.onSave,
|
||||
),
|
||||
SpaceDetailsFailure(:final errorMessage) => _buildErrorDialog(
|
||||
errorMessage,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingDialog() {
|
||||
return AlertDialog(
|
||||
title: widget.title,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SizedBox(
|
||||
height: context.screenHeight * 0.3,
|
||||
width: context.screenWidth * 0.5,
|
||||
child: const Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorDialog(String errorMessage) {
|
||||
return AlertDialog(
|
||||
title: widget.title,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: Center(
|
||||
child: SelectableText(
|
||||
errorMessage,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_devices_box.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_icon_picker.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_name_text_field.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_box.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceDetailsForm extends StatelessWidget {
|
||||
const SpaceDetailsForm({
|
||||
required this.title,
|
||||
required this.space,
|
||||
required this.onSave,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget title;
|
||||
final SpaceDetailsModel space;
|
||||
final void Function(SpaceDetailsModel space) onSave;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => SpaceDetailsModelBloc(initialState: space),
|
||||
child: BlocBuilder<SpaceDetailsModelBloc, SpaceDetailsModel>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, space) {
|
||||
return AlertDialog(
|
||||
title: title,
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: SizedBox(
|
||||
height: context.screenHeight * 0.3,
|
||||
width: context.screenWidth * 0.5,
|
||||
child: Row(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: SpaceIconPicker(iconPath: space.icon)),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SpaceNameTextField(
|
||||
initialValue: space.spaceName,
|
||||
isNameFieldExist: (value) => space.subspaces.any(
|
||||
(subspace) => subspace.name == value,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
SpaceSubSpacesBox(
|
||||
subspaces: space.subspaces,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SpaceDetailsDevicesBox(space: space),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
SpaceDetailsActionButtons(
|
||||
onSave: () => onSave(space),
|
||||
onCancel: Navigator.of(context).pop,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_icon_selection_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceIconPicker extends StatelessWidget {
|
||||
const SpaceIconPicker({
|
||||
required this.iconPath,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String iconPath;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Stack(
|
||||
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: context.screenWidth * 0.175,
|
||||
height: context.screenHeight * 0.175,
|
||||
decoration: const BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: SvgPicture.asset(
|
||||
iconPath,
|
||||
width: context.screenWidth * 0.08,
|
||||
height: context.screenHeight * 0.08,
|
||||
),
|
||||
),
|
||||
Positioned.directional(
|
||||
top: 12,
|
||||
start: context.screenHeight * 0.06,
|
||||
textDirection: Directionality.of(context),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showDialog<String?>(
|
||||
context: context,
|
||||
builder: (context) => SpaceIconSelectionDialog(
|
||||
selectedIcon: iconPath,
|
||||
),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
if (context.mounted) {
|
||||
context.read<SpaceDetailsModelBloc>().add(
|
||||
UpdateSpaceDetailsIcon(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
Assets.iconEdit,
|
||||
width: 16,
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceIconSelectionDialog extends StatelessWidget {
|
||||
const SpaceIconSelectionDialog({super.key, required this.selectedIcon});
|
||||
final String selectedIcon;
|
||||
|
||||
static const List<String> _icons = [
|
||||
Assets.location,
|
||||
Assets.villa,
|
||||
Assets.gym,
|
||||
Assets.sauna,
|
||||
Assets.bbq,
|
||||
Assets.building,
|
||||
Assets.desk,
|
||||
Assets.door,
|
||||
Assets.parking,
|
||||
Assets.pool,
|
||||
Assets.stair,
|
||||
Assets.steamRoom,
|
||||
Assets.street,
|
||||
Assets.unit,
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: SelectableText(
|
||||
'Space Icon',
|
||||
style: context.textTheme.headlineMedium,
|
||||
),
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
content: Container(
|
||||
width: context.screenWidth * 0.45,
|
||||
height: context.screenHeight * 0.275,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 7,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 16,
|
||||
),
|
||||
itemCount: _icons.length,
|
||||
itemBuilder: (context, index) {
|
||||
final isSelected = selectedIcon == _icons[index];
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(2),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: isSelected
|
||||
? Border.all(color: ColorsManager.vividBlue, width: 2)
|
||||
: null,
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(_icons[index]),
|
||||
icon: SvgPicture.asset(
|
||||
_icons[index],
|
||||
width: context.screenWidth * 0.03,
|
||||
height: context.screenHeight * 0.08,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SpaceNameTextField extends StatefulWidget {
|
||||
const SpaceNameTextField({
|
||||
required this.initialValue,
|
||||
required this.isNameFieldExist,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String? initialValue;
|
||||
final bool Function(String value) isNameFieldExist;
|
||||
|
||||
@override
|
||||
State<SpaceNameTextField> createState() => _SpaceNameTextFieldState();
|
||||
}
|
||||
|
||||
class _SpaceNameTextFieldState extends State<SpaceNameTextField> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_controller = TextEditingController(text: widget.initialValue);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
String? _validateName(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '*Space name should not be empty.';
|
||||
}
|
||||
if (widget.isNameFieldExist(value)) {
|
||||
return '*Name already exists';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
onChanged: (value) => context.read<SpaceDetailsModelBloc>().add(
|
||||
UpdateSpaceDetailsName(value),
|
||||
),
|
||||
validator: _validateName,
|
||||
style: context.textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Please enter the name',
|
||||
hintStyle: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: ColorsManager.boxColor,
|
||||
enabledBorder: _buildBorder(context, ColorsManager.vividBlue),
|
||||
focusedBorder: _buildBorder(context, ColorsManager.primaryColor),
|
||||
errorBorder: _buildBorder(context, context.theme.colorScheme.error),
|
||||
focusedErrorBorder: _buildBorder(context, context.theme.colorScheme.error),
|
||||
errorStyle: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
OutlineInputBorder _buildBorder(BuildContext context, [Color? color]) {
|
||||
return OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide(width: 1, color: color ?? ColorsManager.boxColor),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/edit_chip.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/button_content_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_sub_spaces_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_name_display_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/update_space/presentation/bloc/space_details_model_bloc/space_details_model_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class SpaceSubSpacesBox extends StatelessWidget {
|
||||
const SpaceSubSpacesBox({super.key, required this.subspaces});
|
||||
|
||||
final List<Subspace> subspaces;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (subspaces.isEmpty) {
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
overlayColor: ColorsManager.transparentColor,
|
||||
),
|
||||
onPressed: () => _showSubSpacesDialog(context),
|
||||
child: const SizedBox(
|
||||
width: double.infinity,
|
||||
child: ButtonContentWidget(
|
||||
svgAssets: Assets.addIcon,
|
||||
label: 'Create Sub Spaces',
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(
|
||||
color: ColorsManager.textFieldGreyColor,
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
...subspaces.map((e) => SubspaceNameDisplayWidget(subSpace: e)),
|
||||
EditChip(
|
||||
onTap: () => _showSubSpacesDialog(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _showSubSpacesDialog(BuildContext context) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => SpaceSubSpacesDialog(
|
||||
subspaces: subspaces,
|
||||
onSave: (subspaces) {
|
||||
context.read<SpaceDetailsModelBloc>().add(
|
||||
UpdateSpaceDetailsSubspaces(subspaces),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/space_details_action_buttons.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/sub_spaces_input.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class SpaceSubSpacesDialog extends StatefulWidget {
|
||||
const SpaceSubSpacesDialog({
|
||||
required this.subspaces,
|
||||
required this.onSave,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<Subspace> subspaces;
|
||||
final void Function(List<Subspace> subspaces) onSave;
|
||||
|
||||
@override
|
||||
State<SpaceSubSpacesDialog> createState() => _SpaceSubSpacesDialogState();
|
||||
}
|
||||
|
||||
class _SpaceSubSpacesDialogState extends State<SpaceSubSpacesDialog> {
|
||||
late List<Subspace> _subspaces;
|
||||
|
||||
bool get _hasDuplicateNames =>
|
||||
_subspaces.map((subspace) => subspace.name.toLowerCase()).toSet().length !=
|
||||
_subspaces.length;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaces = List.from(widget.subspaces);
|
||||
}
|
||||
|
||||
void _handleSubspaceAdded(String name) {
|
||||
setState(() {
|
||||
_subspaces = [
|
||||
..._subspaces,
|
||||
Subspace(
|
||||
name: name,
|
||||
uuid: const Uuid().v4(),
|
||||
productAllocations: const [],
|
||||
),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
void _handleSubspaceDeleted(String uuid) => setState(
|
||||
() => _subspaces = _subspaces.where((s) => s.uuid != uuid).toList(),
|
||||
);
|
||||
|
||||
void _handleSave() {
|
||||
widget.onSave(_subspaces);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const SelectableText('Create Sub Spaces'),
|
||||
content: Column(
|
||||
spacing: 12,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SubSpacesInput(
|
||||
subSpaces: _subspaces,
|
||||
onSubspaceAdded: _handleSubspaceAdded,
|
||||
onSubspaceDeleted: _handleSubspaceDeleted,
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Visibility(
|
||||
key: ValueKey(_hasDuplicateNames),
|
||||
visible: _hasDuplicateNames,
|
||||
child: const SelectableText(
|
||||
'Error: Duplicate subspace names are not allowed.',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
SpaceDetailsActionButtons(
|
||||
onSave: _hasDuplicateNames ? null : _handleSave,
|
||||
onCancel: Navigator.of(context).pop,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/domain/models/space_details_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/widgets/subspace_chip.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SubSpacesInput extends StatefulWidget {
|
||||
const SubSpacesInput({
|
||||
super.key,
|
||||
required this.subSpaces,
|
||||
required this.onSubspaceAdded,
|
||||
required this.onSubspaceDeleted,
|
||||
});
|
||||
|
||||
final List<Subspace> subSpaces;
|
||||
final void Function(String name) onSubspaceAdded;
|
||||
final void Function(String uuid) onSubspaceDeleted;
|
||||
|
||||
@override
|
||||
State<SubSpacesInput> createState() => _SubSpacesInputState();
|
||||
}
|
||||
|
||||
class _SubSpacesInputState extends State<SubSpacesInput> {
|
||||
late final TextEditingController _subspaceNameController;
|
||||
late final FocusNode _focusNode;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_subspaceNameController = TextEditingController();
|
||||
_focusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subspaceNameController.dispose();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: context.screenWidth * 0.35,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ColorsManager.boxColor,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
...widget.subSpaces.asMap().entries.map(
|
||||
(entry) {
|
||||
final index = entry.key;
|
||||
final subSpace = entry.value;
|
||||
|
||||
final lowerName = subSpace.name.toLowerCase();
|
||||
|
||||
final duplicateIndices = widget.subSpaces
|
||||
.asMap()
|
||||
.entries
|
||||
.where((e) => e.value.name.toLowerCase() == lowerName)
|
||||
.map((e) => e.key)
|
||||
.toList();
|
||||
final isDuplicate = duplicateIndices.length > 1 &&
|
||||
duplicateIndices.indexOf(index) != 0;
|
||||
return SubspaceChip(
|
||||
subSpace: subSpace,
|
||||
isDuplicate: isDuplicate,
|
||||
onDeleted: () => widget.onSubspaceDeleted(subSpace.uuid),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextField(
|
||||
focusNode: _focusNode,
|
||||
controller: _subspaceNameController,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.subSpaces.isEmpty ? 'Please enter the name' : null,
|
||||
hintStyle: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGrayColor,
|
||||
),
|
||||
),
|
||||
onSubmitted: (value) {
|
||||
final trimmedValue = value.trim();
|
||||
if (trimmedValue.isNotEmpty) {
|
||||
widget.onSubspaceAdded(trimmedValue);
|
||||
_subspaceNameController.clear();
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user