mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 17:47:53 +00:00
Compare commits
67 Commits
Implement-
...
Implement-
Author | SHA1 | Date | |
---|---|---|---|
6b8827f4d9 | |||
e8c36f5af6 | |||
762dade195 | |||
f5def4b4d7 | |||
ab326fa820 | |||
e9b4d35f97 | |||
75b9f4a4e6 | |||
fe4063ef8f | |||
029b5d32e0 | |||
428c81efff | |||
288c252f46 | |||
7399dee687 | |||
08e2ed4b4c | |||
59e04708cd | |||
338d4f5737 | |||
5532935a3a | |||
249cbfc242 | |||
8167926620 | |||
559091faa0 | |||
eba351c9be | |||
c112cde634 | |||
65d541d594 | |||
7331c8440b | |||
a409e34643 | |||
7cc59e43df | |||
2681c837f5 | |||
b6664ec1ba | |||
a9895f5462 | |||
6e4f0c3c0c | |||
bbf2891804 | |||
aab2b4a52a | |||
d58da9644f | |||
df46a5b905 | |||
a1fa049a05 | |||
494a000590 | |||
b5d72b2a2a | |||
55a73eee7f | |||
21f8b2962c | |||
645a07287e | |||
df29aab111 | |||
e55e793081 | |||
83202204b0 | |||
d87739f1fd | |||
b128618bfd | |||
5cd083a37b | |||
2b8d987c69 | |||
707cb4791f | |||
03c45ed8d0 | |||
9e0ea4ad6f | |||
9e6b14737f | |||
7c2aed2d58 | |||
bcf62027bc | |||
b001713ce4 | |||
bab3226c73 | |||
fa1eaa570c | |||
42c410d982 | |||
368b1be3c0 | |||
c13119a4e8 | |||
35e9b606b2 | |||
387586f6f7 | |||
7cf4d0b5a9 | |||
e4a27b5651 | |||
f89660a9ff | |||
1a3dc60bd2 | |||
201348a9bf | |||
e2d4e48875 | |||
dcf1df9b4a |
@ -1,2 +1,3 @@
|
|||||||
ENV_NAME=development
|
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
|
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
|
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/
|
||||||
|
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"name": "DEVELOPMENT",
|
"name": "DEVELOPMENT",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -16,19 +11,14 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_dev.dart",
|
"lib/main_dev.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
},
|
||||||
},{
|
{
|
||||||
|
|
||||||
"name": "STAGING",
|
"name": "STAGING",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -36,19 +26,14 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_staging.dart",
|
"lib/main_staging.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
},
|
||||||
},{
|
{
|
||||||
|
|
||||||
"name": "PRODUCTION",
|
"name": "PRODUCTION",
|
||||||
|
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
|
|
||||||
"args": [
|
"args": [
|
||||||
"-d",
|
"-d",
|
||||||
"chrome",
|
"chrome",
|
||||||
@ -56,12 +41,9 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main.dart",
|
"lib/main.dart",
|
||||||
"--web-experimental-hot-reload",
|
"--web-experimental-hot-reload"
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
}
|
||||||
},
|
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
10
assets/icons/add_button_Icon.svg
Normal file
10
assets/icons/add_button_Icon.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 1.4 KiB |
4
assets/icons/back_button_icon.svg
Normal file
4
assets/icons/back_button_icon.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 1.1 KiB |
4
assets/icons/clock_icon.svg
Normal file
4
assets/icons/clock_icon.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 799 B |
24
assets/icons/no_data_table.svg
Normal file
24
assets/icons/no_data_table.svg
Normal file
File diff suppressed because one or more lines are too long
After 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_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.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/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.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');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.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/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.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');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsDev.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -59,7 +61,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:go_router/go_router.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/auth/bloc/auth_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/home/bloc/home_event.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');
|
await dotenv.load(fileName: '.env.$environment');
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptionsStaging.currentPlatform,
|
options: DefaultFirebaseOptions(
|
||||||
|
databaseUrl: dotenv.env['RTDB_URL']!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
initialSetup();
|
initialSetup();
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -56,7 +58,7 @@ class MyApp extends StatelessWidget {
|
|||||||
BlocProvider<CreateRoutineBloc>(
|
BlocProvider<CreateRoutineBloc>(
|
||||||
create: (context) => CreateRoutineBloc(),
|
create: (context) => CreateRoutineBloc(),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (context) => HomeBloc()..add(FetchUserInfo())),
|
BlocProvider(create: (context) => HomeBloc()..add(const FetchUserInfo())),
|
||||||
BlocProvider<VisitorPasswordBloc>(
|
BlocProvider<VisitorPasswordBloc>(
|
||||||
create: (context) => VisitorPasswordBloc(),
|
create: (context) => VisitorPasswordBloc(),
|
||||||
),
|
),
|
||||||
|
@ -18,7 +18,7 @@ class RemoteBookableSpacesService implements BookableSystemService {
|
|||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
path: ApiEndpoints.getBookableSpaces,
|
path: ApiEndpoints.bookableSpaces,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'page': param.page,
|
'page': param.page,
|
||||||
'size': param.size,
|
'size': param.size,
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_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 RemoteCalendarService implements CalendarSystemService {
|
||||||
|
const RemoteCalendarService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
static const _defaultErrorMessage = 'Failed to load Calendar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getCalendarEvents,
|
||||||
|
queryParameters: {
|
||||||
|
'spaceId': spaceId,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return CalendarEventsResponse.fromJson(
|
||||||
|
json as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final responseData = e.response?.data;
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
final errorMessage = responseData['error']?['message'] as String? ??
|
||||||
|
responseData['message'] as String? ??
|
||||||
|
_defaultErrorMessage;
|
||||||
|
throw APIException(errorMessage);
|
||||||
|
}
|
||||||
|
throw APIException(_defaultErrorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException('$_defaultErrorMessage: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeRemoteCalendarService implements CalendarSystemService {
|
||||||
|
const FakeRemoteCalendarService(this._httpService, {this.useDummy = false});
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
final bool useDummy;
|
||||||
|
static const _defaultErrorMessage = 'Failed to load Calendar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
}) async {
|
||||||
|
if (useDummy) {
|
||||||
|
final dummyJson = {
|
||||||
|
'statusCode': 200,
|
||||||
|
'message': 'Successfully fetched all bookings',
|
||||||
|
'data': [
|
||||||
|
{
|
||||||
|
'uuid': 'd4553fa6-a0c9-4f42-81c9-99a13a57bf80',
|
||||||
|
'date': '2025-07-11T10:22:00.626Z',
|
||||||
|
'startTime': '09:00:00',
|
||||||
|
'endTime': '12:00:00',
|
||||||
|
'cost': 10,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||||
|
'date': '2025-07-11T10:22:00.626Z',
|
||||||
|
'startTime': '12:00:00',
|
||||||
|
'endTime': '13:00:00',
|
||||||
|
'cost': 10,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'uuid': 'e9b27af0-b963-4d98-9657-454c4ba78561',
|
||||||
|
'date': '2025-07-13T10:22:00.626Z',
|
||||||
|
'startTime': '15:30:00',
|
||||||
|
'endTime': '19:00:00',
|
||||||
|
'cost': 20,
|
||||||
|
'user': {
|
||||||
|
'uuid': '784394ff-3197-4c39-9f07-48dc44920b1e',
|
||||||
|
'firstName': 'salsabeel',
|
||||||
|
'lastName': 'abuzaid',
|
||||||
|
'email': 'test@test.com',
|
||||||
|
'companyName': null
|
||||||
|
},
|
||||||
|
'space': {
|
||||||
|
'uuid': '000f4d81-43e4-4ad7-865c-0f8b04b7081e',
|
||||||
|
'spaceName': '2(1)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'success': true
|
||||||
|
};
|
||||||
|
final response = CalendarEventsResponse.fromJson(dummyJson);
|
||||||
|
|
||||||
|
// Filter events by spaceId
|
||||||
|
final filteredData = response.data.where((event) {
|
||||||
|
return event.space.uuid == spaceId;
|
||||||
|
}).toList();
|
||||||
|
print('Filtering events for spaceId: $spaceId');
|
||||||
|
print('Found ${filteredData.length} matching events');
|
||||||
|
return filteredData.isNotEmpty
|
||||||
|
? CalendarEventsResponse(
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
message: response.message,
|
||||||
|
data: filteredData,
|
||||||
|
success: response.success,
|
||||||
|
)
|
||||||
|
: CalendarEventsResponse(
|
||||||
|
statusCode: 404,
|
||||||
|
message: 'No events found for spaceId: $spaceId',
|
||||||
|
data: [],
|
||||||
|
success: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: ApiEndpoints.getCalendarEvents,
|
||||||
|
queryParameters: {
|
||||||
|
'spaceId': spaceId,
|
||||||
|
},
|
||||||
|
expectedResponseModel: (json) {
|
||||||
|
return CalendarEventsResponse.fromJson(
|
||||||
|
json as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventsResponse.fromJson(response as Map<String, dynamic>);
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final responseData = e.response?.data;
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
final errorMessage = responseData['error']?['message'] as String? ??
|
||||||
|
responseData['message'] as String? ??
|
||||||
|
_defaultErrorMessage;
|
||||||
|
throw APIException(errorMessage);
|
||||||
|
}
|
||||||
|
throw APIException(_defaultErrorMessage);
|
||||||
|
} catch (e) {
|
||||||
|
throw APIException('$_defaultErrorMessage: ${e.toString()}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
class CalendarEventBooking {
|
||||||
|
final String uuid;
|
||||||
|
final DateTime date;
|
||||||
|
final String startTime;
|
||||||
|
final String endTime;
|
||||||
|
final int cost;
|
||||||
|
final BookingUser user;
|
||||||
|
final BookingSpace space;
|
||||||
|
|
||||||
|
CalendarEventBooking({
|
||||||
|
required this.uuid,
|
||||||
|
required this.date,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
required this.cost,
|
||||||
|
required this.user,
|
||||||
|
required this.space,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CalendarEventBooking.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CalendarEventBooking(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
date: json['date'] != null
|
||||||
|
? DateTime.parse(json['date'] as String)
|
||||||
|
: DateTime.now(),
|
||||||
|
startTime: json['startTime'] as String? ?? '',
|
||||||
|
endTime: json['endTime'] as String? ?? '',
|
||||||
|
cost: _parseInt(json['cost']),
|
||||||
|
user: json['user'] != null
|
||||||
|
? BookingUser.fromJson(json['user'] as Map<String, dynamic>)
|
||||||
|
: BookingUser.empty(),
|
||||||
|
space: json['space'] != null
|
||||||
|
? BookingSpace.fromJson(json['space'] as Map<String, dynamic>)
|
||||||
|
: BookingSpace.empty(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _parseInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is String) return int.tryParse(value) ?? 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookingUser {
|
||||||
|
final String uuid;
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String email;
|
||||||
|
final String? companyName;
|
||||||
|
|
||||||
|
BookingUser({
|
||||||
|
required this.uuid,
|
||||||
|
required this.firstName,
|
||||||
|
required this.lastName,
|
||||||
|
required this.email,
|
||||||
|
this.companyName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BookingUser.fromJson(Map<String, dynamic> json) {
|
||||||
|
return BookingUser(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
firstName: json['firstName'] as String? ?? '',
|
||||||
|
lastName: json['lastName'] as String? ?? '',
|
||||||
|
email: json['email'] as String? ?? '',
|
||||||
|
companyName: json['companyName'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BookingUser.empty() {
|
||||||
|
return BookingUser(
|
||||||
|
uuid: '',
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
companyName: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookingSpace {
|
||||||
|
final String uuid;
|
||||||
|
final String spaceName;
|
||||||
|
|
||||||
|
BookingSpace({
|
||||||
|
required this.uuid,
|
||||||
|
required this.spaceName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BookingSpace.fromJson(Map<String, dynamic> json) {
|
||||||
|
return BookingSpace(
|
||||||
|
uuid: json['uuid'] as String? ?? '',
|
||||||
|
spaceName: json['spaceName'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BookingSpace.empty() {
|
||||||
|
return BookingSpace(
|
||||||
|
uuid: '',
|
||||||
|
spaceName: '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CalendarEventsResponse {
|
||||||
|
final int statusCode;
|
||||||
|
final String message;
|
||||||
|
final List<CalendarEventBooking> data;
|
||||||
|
final bool success;
|
||||||
|
|
||||||
|
CalendarEventsResponse({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.message,
|
||||||
|
required this.data,
|
||||||
|
required this.success,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CalendarEventsResponse.fromJson(Map<String, dynamic> json) {
|
||||||
|
return CalendarEventsResponse(
|
||||||
|
statusCode: _parseInt(json['statusCode']),
|
||||||
|
message: json['message'] as String? ?? '',
|
||||||
|
data: (json['data'] as List? ?? [])
|
||||||
|
.map((e) => CalendarEventBooking.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
success: json['success'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _parseInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is String) return int.tryParse(value) ?? 0;
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
|
||||||
|
abstract class CalendarSystemService {
|
||||||
|
Future<CalendarEventsResponse> getCalendarEvents({
|
||||||
|
required String spaceId,
|
||||||
|
});
|
||||||
|
}
|
@ -2,13 +2,17 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/models/calendar_event_booking.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/domain/services/calendar_system_service.dart';
|
||||||
|
|
||||||
part 'events_event.dart';
|
part 'events_event.dart';
|
||||||
part 'events_state.dart';
|
part 'events_state.dart';
|
||||||
|
|
||||||
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
||||||
final EventController eventController = EventController();
|
final EventController eventController = EventController();
|
||||||
|
final CalendarSystemService calendarService;
|
||||||
|
|
||||||
CalendarEventsBloc() : super(EventsInitial()) {
|
CalendarEventsBloc({required this.calendarService}) : super(EventsInitial()) {
|
||||||
on<LoadEvents>(_onLoadEvents);
|
on<LoadEvents>(_onLoadEvents);
|
||||||
on<AddEvent>(_onAddEvent);
|
on<AddEvent>(_onAddEvent);
|
||||||
on<StartTimer>(_onStartTimer);
|
on<StartTimer>(_onStartTimer);
|
||||||
@ -22,53 +26,24 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
) async {
|
) async {
|
||||||
emit(EventsLoading());
|
emit(EventsLoading());
|
||||||
try {
|
try {
|
||||||
final events = _generateDummyEventsForWeek(event.weekStart);
|
final response = await calendarService.getCalendarEvents(
|
||||||
|
spaceId: event.spaceId,
|
||||||
|
);
|
||||||
|
final events =
|
||||||
|
response.data.map<CalendarEventData>(_toCalendarEventData).toList();
|
||||||
eventController.addAll(events);
|
eventController.addAll(events);
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(events: events));
|
||||||
events: events,
|
|
||||||
initialDate: event.weekStart,
|
|
||||||
weekDays: _getWeekDays(event.weekStart),
|
|
||||||
));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(EventsError('Failed to load events'));
|
emit(EventsError('Failed to load events'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
|
||||||
final events = <CalendarEventData>[];
|
|
||||||
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
final date = weekStart.add(Duration(days: i));
|
|
||||||
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 9, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 10, minute: 30),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Daily standup',
|
|
||||||
color: Colors.blue,
|
|
||||||
));
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 14, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 15, minute: 0),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
void _onAddEvent(AddEvent event, Emitter<CalendarEventState> emit) {
|
||||||
eventController.add(event.event);
|
eventController.add(event.event);
|
||||||
if (state is EventsLoaded) {
|
if (state is EventsLoaded) {
|
||||||
final loaded = state as EventsLoaded;
|
final loaded = state as EventsLoaded;
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(
|
||||||
events: [...eventController.events],
|
events: [...eventController.events],
|
||||||
initialDate: loaded.initialDate,
|
|
||||||
weekDays: loaded.weekDays,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,47 +61,44 @@ class CalendarEventsBloc extends Bloc<CalendarEventsEvent, CalendarEventState> {
|
|||||||
final newWeekDays = _getWeekDays(event.weekDate);
|
final newWeekDays = _getWeekDays(event.weekDate);
|
||||||
emit(EventsLoaded(
|
emit(EventsLoaded(
|
||||||
events: loaded.events,
|
events: loaded.events,
|
||||||
initialDate: event.weekDate,
|
|
||||||
weekDays: newWeekDays,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEvents() {
|
CalendarEventData _toCalendarEventData(CalendarEventBooking booking) {
|
||||||
final now = DateTime.now();
|
final date = booking.date;
|
||||||
return [
|
|
||||||
CalendarEventData(
|
final localDate = date.toLocal();
|
||||||
date: now,
|
|
||||||
startTime: now.copyWith(hour: 8, minute: 00, second: 0),
|
final startParts = booking.startTime.split(':').map(int.parse).toList();
|
||||||
endTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
final endParts = booking.endTime.split(':').map(int.parse).toList();
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Weekly team sync',
|
final startTime = DateTime(
|
||||||
|
localDate.year,
|
||||||
|
localDate.month,
|
||||||
|
localDate.day,
|
||||||
|
startParts[0],
|
||||||
|
startParts[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
final endTime = DateTime(
|
||||||
|
localDate.year,
|
||||||
|
localDate.month,
|
||||||
|
localDate.day,
|
||||||
|
endParts[0],
|
||||||
|
endParts[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
return CalendarEventData(
|
||||||
|
date: startTime,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
title:
|
||||||
|
'${booking.space.spaceName} - ${booking.user.firstName} ${booking.user.lastName}',
|
||||||
|
description: 'Cost: ${booking.cost}',
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
),
|
event: booking,
|
||||||
CalendarEventData(
|
);
|
||||||
date: now,
|
|
||||||
startTime: now.copyWith(hour: 9, minute: 00, second: 0),
|
|
||||||
endTime: now.copyWith(hour: 10, minute: 30, second: 0),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Weekly team sync',
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
CalendarEventData(
|
|
||||||
date: now.add(const Duration(days: 1)),
|
|
||||||
startTime: now.copyWith(hour: 14, day: now.day + 1),
|
|
||||||
endTime: now.copyWith(hour: 15, day: now.day + 1),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
|
||||||
CalendarEventData(
|
|
||||||
date: now.add(const Duration(days: 2)),
|
|
||||||
startTime: now.copyWith(hour: 11, day: now.day + 2),
|
|
||||||
endTime: now.copyWith(hour: 12, day: now.day + 2),
|
|
||||||
title: 'Lunch with Team',
|
|
||||||
color: Colors.orange,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DateTime> _getWeekDays(DateTime date) {
|
List<DateTime> _getWeekDays(DateTime date) {
|
||||||
|
@ -6,13 +6,20 @@ abstract class CalendarEventsEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadEvents extends CalendarEventsEvent {
|
class LoadEvents extends CalendarEventsEvent {
|
||||||
|
final String spaceId;
|
||||||
final DateTime weekStart;
|
final DateTime weekStart;
|
||||||
const LoadEvents({required this.weekStart});
|
final DateTime weekEnd;
|
||||||
|
|
||||||
|
const LoadEvents({
|
||||||
|
required this.spaceId,
|
||||||
|
required this.weekStart,
|
||||||
|
required this.weekEnd,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class AddEvent extends CalendarEventsEvent {
|
class AddEvent extends CalendarEventsEvent {
|
||||||
final CalendarEventData event;
|
final CalendarEventData event;
|
||||||
AddEvent(this.event);
|
const AddEvent(this.event);
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartTimer extends CalendarEventsEvent {}
|
class StartTimer extends CalendarEventsEvent {}
|
||||||
@ -23,3 +30,8 @@ class GoToWeek extends CalendarEventsEvent {
|
|||||||
final DateTime weekDate;
|
final DateTime weekDate;
|
||||||
GoToWeek(this.weekDate);
|
GoToWeek(this.weekDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CheckWeekHasEvents extends CalendarEventsEvent {
|
||||||
|
final DateTime weekStart;
|
||||||
|
const CheckWeekHasEvents(this.weekStart);
|
||||||
|
}
|
||||||
|
@ -9,13 +9,9 @@ class EventsLoading extends CalendarEventState {}
|
|||||||
|
|
||||||
class EventsLoaded extends CalendarEventState {
|
class EventsLoaded extends CalendarEventState {
|
||||||
final List<CalendarEventData> events;
|
final List<CalendarEventData> events;
|
||||||
final DateTime initialDate;
|
|
||||||
final List<DateTime> weekDays;
|
|
||||||
|
|
||||||
EventsLoaded({
|
EventsLoaded({
|
||||||
required this.events,
|
required this.events,
|
||||||
required this.initialDate,
|
|
||||||
required this.weekDays,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,12 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
||||||
import 'date_selection_state.dart';
|
import 'date_selection_state.dart';
|
||||||
|
|
||||||
|
|
||||||
class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
||||||
DateSelectionBloc() : super(DateSelectionState.initial()) {
|
DateSelectionBloc() : super(DateSelectionState.initial()) {
|
||||||
on<SelectDate>((event, emit) {
|
on<SelectDate>((event, emit) {
|
||||||
final newWeekStart = _getStartOfWeek(event.selectedDate);
|
final newWeekStart = _getStartOfWeek(event.selectedDate);
|
||||||
emit(DateSelectionState(
|
emit(state.copyWith(
|
||||||
selectedDate: event.selectedDate,
|
selectedDate: event.selectedDate,
|
||||||
weekStart: newWeekStart,
|
weekStart: newWeekStart,
|
||||||
));
|
));
|
||||||
@ -14,19 +15,21 @@ class DateSelectionBloc extends Bloc<DateSelectionEvent, DateSelectionState> {
|
|||||||
|
|
||||||
on<NextWeek>((event, emit) {
|
on<NextWeek>((event, emit) {
|
||||||
final newWeekStart = state.weekStart.add(const Duration(days: 7));
|
final newWeekStart = state.weekStart.add(const Duration(days: 7));
|
||||||
final inNewWeek = state.selectedDate
|
emit(state.copyWith(
|
||||||
.isAfter(newWeekStart.subtract(const Duration(days: 1))) &&
|
|
||||||
state.selectedDate
|
|
||||||
.isBefore(newWeekStart.add(const Duration(days: 7)));
|
|
||||||
emit(DateSelectionState(
|
|
||||||
selectedDate: state.selectedDate,
|
|
||||||
weekStart: newWeekStart,
|
weekStart: newWeekStart,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
on<PreviousWeek>((event, emit) {
|
on<PreviousWeek>((event, emit) {
|
||||||
emit(DateSelectionState(
|
final newWeekStart = state.weekStart.subtract(const Duration(days: 7));
|
||||||
selectedDate: state.selectedDate!.subtract(const Duration(days: 7)),
|
emit(state.copyWith(
|
||||||
weekStart: state.weekStart.subtract(const Duration(days: 7)),
|
weekStart: newWeekStart,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
on<SelectDateFromSidebarCalendar>((event, emit) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
selectedDateFromSideBarCalender: event.selectedDate,
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,3 +11,8 @@ class SelectDate extends DateSelectionEvent {
|
|||||||
class NextWeek extends DateSelectionEvent {}
|
class NextWeek extends DateSelectionEvent {}
|
||||||
|
|
||||||
class PreviousWeek extends DateSelectionEvent {}
|
class PreviousWeek extends DateSelectionEvent {}
|
||||||
|
|
||||||
|
class SelectDateFromSidebarCalendar extends DateSelectionEvent {
|
||||||
|
final DateTime selectedDate;
|
||||||
|
SelectDateFromSidebarCalendar(this.selectedDate);
|
||||||
|
}
|
@ -1,21 +1,34 @@
|
|||||||
|
|
||||||
class DateSelectionState {
|
class DateSelectionState {
|
||||||
final DateTime selectedDate;
|
final DateTime selectedDate;
|
||||||
final DateTime weekStart;
|
final DateTime weekStart;
|
||||||
|
final DateTime? selectedDateFromSideBarCalender;
|
||||||
|
|
||||||
const DateSelectionState({
|
DateSelectionState({
|
||||||
required this.selectedDate,
|
required this.selectedDate,
|
||||||
required this.weekStart,
|
required this.weekStart,
|
||||||
|
this.selectedDateFromSideBarCalender,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory DateSelectionState.initial() {
|
factory DateSelectionState.initial() {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
final weekStart = now.subtract(Duration(days: now.weekday - 1));
|
||||||
return DateSelectionState(
|
return DateSelectionState(
|
||||||
selectedDate: now,
|
selectedDate: now,
|
||||||
weekStart: _getStartOfWeek(now),
|
weekStart: weekStart,
|
||||||
|
selectedDateFromSideBarCalender: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static DateTime _getStartOfWeek(DateTime date) {
|
DateSelectionState copyWith({
|
||||||
return date.subtract(Duration(days: date.weekday - 1));
|
DateTime? selectedDate,
|
||||||
|
DateTime? weekStart,
|
||||||
|
DateTime? selectedDateFromSideBarCalender,
|
||||||
|
}) {
|
||||||
|
return DateSelectionState(
|
||||||
|
selectedDate: selectedDate ?? this.selectedDate,
|
||||||
|
weekStart: weekStart ?? this.weekStart,
|
||||||
|
selectedDateFromSideBarCalender: selectedDateFromSideBarCalender ?? this.selectedDateFromSideBarCalender,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/data/services/remote_calendar_service.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/calendar/events_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_event.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/bloc/date_selection/date_selection_state.dart';
|
||||||
@ -9,7 +10,9 @@ import 'package:syncrow_web/pages/access_management/booking_system/presentation/
|
|||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/booking_sidebar.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/custom_calendar_page.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/icon_text_button.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_navigation.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/weekly_calendar_page.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
|
||||||
@ -35,33 +38,20 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CalendarEventData> _generateDummyEventsForWeek(DateTime weekStart) {
|
void _dispatchLoadEvents(BuildContext context) {
|
||||||
final List<CalendarEventData> events = [];
|
final selectedRoom =
|
||||||
for (int i = 0; i < 7; i++) {
|
context.read<SelectedBookableSpaceBloc>().state.selectedBookableSpace;
|
||||||
final date = weekStart.add(Duration(days: i));
|
final dateState = context.read<DateSelectionBloc>().state;
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 9, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 10, minute: 30),
|
|
||||||
title: 'Team Meeting',
|
|
||||||
description: 'Daily standup',
|
|
||||||
color: Colors.blue,
|
|
||||||
));
|
|
||||||
events.add(CalendarEventData(
|
|
||||||
date: date,
|
|
||||||
startTime: date.copyWith(hour: 14, minute: 0),
|
|
||||||
endTime: date.copyWith(hour: 15, minute: 0),
|
|
||||||
title: 'Client Call',
|
|
||||||
description: 'Project discussion',
|
|
||||||
color: Colors.green,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _loadEventsForWeek(DateTime weekStart) {
|
if (selectedRoom != null) {
|
||||||
_eventController.removeWhere((_) => true);
|
context.read<CalendarEventsBloc>().add(
|
||||||
_eventController.addAll(_generateDummyEventsForWeek(weekStart));
|
LoadEvents(
|
||||||
|
spaceId: selectedRoom.uuid,
|
||||||
|
weekStart: dateState.weekStart,
|
||||||
|
weekEnd: dateState.weekStart.add(const Duration(days: 6)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,13 +60,28 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
BlocProvider(create: (_) => SelectedBookableSpaceBloc()),
|
||||||
BlocProvider(create: (_) => DateSelectionBloc()),
|
BlocProvider(create: (_) => DateSelectionBloc()),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) => CalendarEventsBloc(
|
||||||
|
calendarService:
|
||||||
|
FakeRemoteCalendarService(HTTPService(), useDummy: true),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
child: Builder(
|
||||||
listenWhen: (previous, current) =>
|
builder: (context) =>
|
||||||
previous.weekStart != current.weekStart,
|
BlocListener<CalendarEventsBloc, CalendarEventState>(
|
||||||
|
listenWhen: (prev, curr) => curr is EventsLoaded,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_loadEventsForWeek(state.weekStart);
|
if (state is EventsLoaded) {
|
||||||
|
_eventController.removeWhere((_) => true);
|
||||||
|
_eventController.addAll(state.events);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
child: BlocListener<SelectedBookableSpaceBloc,
|
||||||
|
SelectedBookableSpaceState>(
|
||||||
|
listener: (context, state) => _dispatchLoadEvents(context),
|
||||||
|
child: BlocListener<DateSelectionBloc, DateSelectionState>(
|
||||||
|
listener: (context, state) => _dispatchLoadEvents(context),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -111,7 +116,8 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
child: BlocBuilder<DateSelectionBloc,
|
||||||
|
DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return CustomCalendarPage(
|
return CustomCalendarPage(
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
@ -120,6 +126,8 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(SelectDate(newDate));
|
.add(SelectDate(newDate));
|
||||||
|
context.read<DateSelectionBloc>().add(
|
||||||
|
SelectDateFromSidebarCalendar(newDate));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -154,59 +162,25 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
BlocBuilder<DateSelectionBloc, DateSelectionState>(
|
BlocBuilder<DateSelectionBloc,
|
||||||
|
DateSelectionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final weekStart = state.weekStart;
|
final weekStart = state.weekStart;
|
||||||
final weekEnd =
|
final weekEnd =
|
||||||
weekStart.add(const Duration(days: 6));
|
weekStart.add(const Duration(days: 6));
|
||||||
return Container(
|
return WeekNavigation(
|
||||||
padding: const EdgeInsets.symmetric(
|
weekStart: weekStart,
|
||||||
horizontal: 10, vertical: 5),
|
weekEnd: weekEnd,
|
||||||
decoration: BoxDecoration(
|
onPreviousWeek: () {
|
||||||
color: ColorsManager.circleRolesBackground,
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
blurRadius: 4,
|
|
||||||
offset: Offset(0, 1),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
iconSize: 15,
|
|
||||||
icon: const Icon(Icons.arrow_back_ios,
|
|
||||||
color: ColorsManager.lightGrayColor),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(PreviousWeek());
|
.add(PreviousWeek());
|
||||||
},
|
},
|
||||||
),
|
onNextWeek: () {
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(
|
|
||||||
_getMonthYearText(weekStart, weekEnd),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: ColorsManager.lightGrayColor,
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
IconButton(
|
|
||||||
iconSize: 15,
|
|
||||||
icon: const Icon(Icons.arrow_forward_ios,
|
|
||||||
color: ColorsManager.lightGrayColor),
|
|
||||||
onPressed: () {
|
|
||||||
context
|
context
|
||||||
.read<DateSelectionBloc>()
|
.read<DateSelectionBloc>()
|
||||||
.add(NextWeek());
|
.add(NextWeek());
|
||||||
},
|
},
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -216,17 +190,35 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
child: BlocBuilder<SelectedBookableSpaceBloc,
|
child: BlocBuilder<SelectedBookableSpaceBloc,
|
||||||
SelectedBookableSpaceState>(
|
SelectedBookableSpaceState>(
|
||||||
builder: (context, roomState) {
|
builder: (context, roomState) {
|
||||||
final selectedRoom = roomState.selectedBookableSpace;
|
final selectedRoom =
|
||||||
|
roomState.selectedBookableSpace;
|
||||||
return BlocBuilder<DateSelectionBloc,
|
return BlocBuilder<DateSelectionBloc,
|
||||||
DateSelectionState>(
|
DateSelectionState>(
|
||||||
builder: (context, dateState) {
|
builder: (context, dateState) {
|
||||||
return WeeklyCalendarPage(
|
return BlocListener<CalendarEventsBloc,
|
||||||
startTime:
|
CalendarEventState>(
|
||||||
selectedRoom?.bookableConfig.startTime,
|
listenWhen: (prev, curr) =>
|
||||||
endTime: selectedRoom?.bookableConfig.endTime,
|
curr is EventsLoaded,
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is EventsLoaded) {
|
||||||
|
_eventController
|
||||||
|
.removeWhere((_) => true);
|
||||||
|
_eventController.addAll(state.events);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: WeeklyCalendarPage(
|
||||||
|
startTime: selectedRoom
|
||||||
|
?.bookableConfig.startTime,
|
||||||
|
endTime: selectedRoom
|
||||||
|
?.bookableConfig.endTime,
|
||||||
weekStart: dateState.weekStart,
|
weekStart: dateState.weekStart,
|
||||||
selectedDate: dateState.selectedDate,
|
selectedDate: dateState.selectedDate,
|
||||||
eventController: _eventController,
|
eventController: _eventController,
|
||||||
|
selectedDateFromSideBarCalender: context
|
||||||
|
.watch<DateSelectionBloc>()
|
||||||
|
.state
|
||||||
|
.selectedDateFromSideBarCalender,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -240,20 +232,9 @@ class _BookingPageState extends State<BookingPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getMonthYearText(DateTime start, DateTime end) {
|
|
||||||
final startMonth = DateFormat('MMM').format(start);
|
|
||||||
final endMonth = DateFormat('MMM').format(end);
|
|
||||||
final year = start.year == end.year
|
|
||||||
? start.year.toString()
|
|
||||||
: '${start.year}-${end.year}';
|
|
||||||
|
|
||||||
if (start.month == end.month) {
|
|
||||||
return '$startMonth $year';
|
|
||||||
} else {
|
|
||||||
return '$startMonth - $endMonth $year';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class EventTileWidget extends StatelessWidget {
|
||||||
|
final List<CalendarEventData<Object?>> events;
|
||||||
|
|
||||||
|
const EventTileWidget({
|
||||||
|
super.key,
|
||||||
|
required this.events,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: events.map((event) {
|
||||||
|
final bool isEventEnded =
|
||||||
|
event.endTime != null && event.endTime!.isBefore(DateTime.now());
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isEventEnded
|
||||||
|
? ColorsManager.lightGrayBorderColor
|
||||||
|
: ColorsManager.blue1.withOpacity(0.25),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('h:mm a').format(event.startTime!),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
event.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
class HatchedColumnBackground extends StatelessWidget {
|
||||||
|
final Color backgroundColor;
|
||||||
|
final Color lineColor;
|
||||||
|
final double opacity;
|
||||||
|
final double stripeSpacing;
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
|
||||||
|
const HatchedColumnBackground({
|
||||||
|
super.key,
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.lineColor,
|
||||||
|
this.opacity = 0.15,
|
||||||
|
this.stripeSpacing = 12,
|
||||||
|
this.borderRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: _HatchedBackgroundPainter(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
opacity: opacity,
|
||||||
|
lineColor: lineColor,
|
||||||
|
stripeSpacing: stripeSpacing,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HatchedBackgroundPainter extends CustomPainter {
|
||||||
|
final Color backgroundColor;
|
||||||
|
final double opacity;
|
||||||
|
final Color lineColor;
|
||||||
|
final double stripeSpacing;
|
||||||
|
final BorderRadius? borderRadius;
|
||||||
|
|
||||||
|
_HatchedBackgroundPainter({
|
||||||
|
required this.backgroundColor,
|
||||||
|
required this.opacity,
|
||||||
|
required this.lineColor,
|
||||||
|
required this.stripeSpacing,
|
||||||
|
this.borderRadius,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final rect = Rect.fromLTWH(0, 0, size.width, size.height);
|
||||||
|
final RRect rrect = borderRadius?.toRRect(rect) ??
|
||||||
|
RRect.fromRectAndRadius(rect, Radius.zero);
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = backgroundColor.withOpacity(0.02)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
canvas.drawRRect(rrect, backgroundPaint);
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRRect(rrect);
|
||||||
|
final linePaint = Paint()
|
||||||
|
..color = lineColor
|
||||||
|
..strokeWidth = 0.5
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
final maxExtent =
|
||||||
|
math.sqrt(size.width * size.width + size.height * size.height);
|
||||||
|
|
||||||
|
canvas.translate(0, size.height);
|
||||||
|
canvas.rotate(-math.pi / 4);
|
||||||
|
double y = -maxExtent;
|
||||||
|
while (y < maxExtent) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(-maxExtent, y),
|
||||||
|
Offset(maxExtent, y),
|
||||||
|
linePaint,
|
||||||
|
);
|
||||||
|
y += stripeSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _HatchedBackgroundPainter oldDelegate) {
|
||||||
|
return backgroundColor != oldDelegate.backgroundColor ||
|
||||||
|
opacity != oldDelegate.opacity ||
|
||||||
|
lineColor != oldDelegate.lineColor ||
|
||||||
|
stripeSpacing != oldDelegate.stripeSpacing ||
|
||||||
|
borderRadius != oldDelegate.borderRadius;
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,13 @@ class SvgTextButton extends StatelessWidget {
|
|||||||
final double borderRadius;
|
final double borderRadius;
|
||||||
final List<BoxShadow> boxShadow;
|
final List<BoxShadow> boxShadow;
|
||||||
final double svgSize;
|
final double svgSize;
|
||||||
|
final double? fontSize;
|
||||||
|
final FontWeight? fontWeight;
|
||||||
const SvgTextButton({
|
const SvgTextButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.svgAsset,
|
required this.svgAsset,
|
||||||
|
this.fontSize,
|
||||||
|
this.fontWeight,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
this.backgroundColor = ColorsManager.circleRolesBackground,
|
this.backgroundColor = ColorsManager.circleRolesBackground,
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class TimeLineWidget extends StatelessWidget {
|
||||||
|
final DateTime date;
|
||||||
|
|
||||||
|
const TimeLineWidget({Key? key, required this.date}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
int hour =
|
||||||
|
date.hour == 0 ? 12 : (date.hour > 12 ? date.hour - 12 : date.hour);
|
||||||
|
String period = date.hour >= 12 ? 'PM' : 'AM';
|
||||||
|
return Container(
|
||||||
|
height: 60,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '$hour',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 24,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 2, top: 6),
|
||||||
|
child: Text(
|
||||||
|
period,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: PlaceholderAlignment.baseline,
|
||||||
|
baseline: TextBaseline.alphabetic,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class WeekDayHeader extends StatelessWidget {
|
||||||
|
final DateTime date;
|
||||||
|
final bool isSelectedDay;
|
||||||
|
|
||||||
|
const WeekDayHeader({
|
||||||
|
Key? key,
|
||||||
|
required this.date,
|
||||||
|
required this.isSelectedDay,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat('EEE').format(date).toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 14,
|
||||||
|
color: isSelectedDay ? Colors.blue : Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
DateFormat('d').format(date),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 20,
|
||||||
|
color:
|
||||||
|
isSelectedDay ? ColorsManager.blue1 : ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
|
class WeekNavigation extends StatelessWidget {
|
||||||
|
final DateTime weekStart;
|
||||||
|
final DateTime weekEnd;
|
||||||
|
final VoidCallback onPreviousWeek;
|
||||||
|
final VoidCallback onNextWeek;
|
||||||
|
|
||||||
|
const WeekNavigation({
|
||||||
|
Key? key,
|
||||||
|
required this.weekStart,
|
||||||
|
required this.weekEnd,
|
||||||
|
required this.onPreviousWeek,
|
||||||
|
required this.onNextWeek,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: ColorsManager.circleRolesBackground,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
iconSize: 15,
|
||||||
|
icon: const Icon(Icons.arrow_back_ios,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
onPressed: onPreviousWeek,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
_getMonthYearText(weekStart, weekEnd),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
IconButton(
|
||||||
|
iconSize: 15,
|
||||||
|
icon: const Icon(Icons.arrow_forward_ios,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
onPressed: onNextWeek,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getMonthYearText(DateTime start, DateTime end) {
|
||||||
|
final startMonth = DateFormat('MMM').format(start);
|
||||||
|
final endMonth = DateFormat('MMM').format(end);
|
||||||
|
final year = start.year == end.year
|
||||||
|
? start.year.toString()
|
||||||
|
: '${start.year}-${end.year}';
|
||||||
|
|
||||||
|
if (start.month == end.month) {
|
||||||
|
return '$startMonth $year';
|
||||||
|
} else {
|
||||||
|
return '$startMonth - $endMonth $year';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/event_tile_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/hatched_column_background.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/time_line_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/widgets/week_day_header.dart';
|
||||||
import 'package:syncrow_web/utils/color_manager.dart';
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class WeeklyCalendarPage extends StatelessWidget {
|
class WeeklyCalendarPage extends StatelessWidget {
|
||||||
@ -9,6 +12,7 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
final EventController eventController;
|
final EventController eventController;
|
||||||
final String? startTime;
|
final String? startTime;
|
||||||
final String? endTime;
|
final String? endTime;
|
||||||
|
final DateTime? selectedDateFromSideBarCalender;
|
||||||
|
|
||||||
const WeeklyCalendarPage({
|
const WeeklyCalendarPage({
|
||||||
super.key,
|
super.key,
|
||||||
@ -17,37 +21,81 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
required this.eventController,
|
required this.eventController,
|
||||||
this.startTime,
|
this.startTime,
|
||||||
this.endTime,
|
this.endTime,
|
||||||
|
this.selectedDateFromSideBarCalender,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final startHour = _parseHour(startTime, defaultValue: 0);
|
final startHour = _parseHour(startTime, defaultValue: 0);
|
||||||
final endHour = _parseHour(endTime, defaultValue: 24);
|
final endHour = _parseHour(endTime, defaultValue: 24);
|
||||||
|
|
||||||
if (endTime == null || endTime!.isEmpty) {
|
if (endTime == null || endTime!.isEmpty) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: Text(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.calendar_today,
|
||||||
|
color: ColorsManager.lightGrayColor,
|
||||||
|
size: 80,
|
||||||
|
),
|
||||||
|
SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
'Please select a bookable space to view the calendar.',
|
'Please select a bookable space to view the calendar.',
|
||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: ColorsManager.lightGrayColor),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final weekDays = _getWeekDays(weekStart);
|
final weekDays = _getWeekDays(weekStart);
|
||||||
|
|
||||||
|
final selectedDayIndex =
|
||||||
|
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
||||||
|
final selectedSidebarIndex = selectedDateFromSideBarCalender == null
|
||||||
|
? -1
|
||||||
|
: weekDays
|
||||||
|
.indexWhere((d) => isSameDay(d, selectedDateFromSideBarCalender!));
|
||||||
|
|
||||||
|
const double timeLineWidth = 80;
|
||||||
|
const int totalDays = 7;
|
||||||
|
final DateTime highlightStart = DateTime(2025, 7, 10);
|
||||||
|
final DateTime highlightEnd = DateTime(2025, 7, 19);
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final double calendarWidth = constraints.maxWidth;
|
final double calendarWidth = constraints.maxWidth;
|
||||||
const double timeLineWidth = 80;
|
|
||||||
const int totalDays = 7;
|
|
||||||
final double dayColumnWidth =
|
final double dayColumnWidth =
|
||||||
(calendarWidth - timeLineWidth) / totalDays - 0.1;
|
(calendarWidth - timeLineWidth) / totalDays - 0.1;
|
||||||
final selectedDayIndex =
|
bool isInRange(DateTime date, DateTime start, DateTime end) {
|
||||||
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
return !date.isBefore(start) && !date.isAfter(end);
|
||||||
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
padding: const EdgeInsets.only(left: 25.0, right: 25.0, top: 25),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
WeekView(
|
WeekView(
|
||||||
|
weekDetectorBuilder: ({
|
||||||
|
required date,
|
||||||
|
required height,
|
||||||
|
required heightPerMinute,
|
||||||
|
required minuteSlotSize,
|
||||||
|
required width,
|
||||||
|
}) {
|
||||||
|
return isInRange(date, highlightStart, highlightEnd)
|
||||||
|
? HatchedColumnBackground(
|
||||||
|
backgroundColor: ColorsManager.grey800,
|
||||||
|
lineColor: ColorsManager.textGray,
|
||||||
|
opacity: 0.3,
|
||||||
|
stripeSpacing: 12,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
)
|
||||||
|
: const SizedBox();
|
||||||
|
},
|
||||||
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
pageViewPhysics: const NeverScrollableScrollPhysics(),
|
||||||
key: ValueKey(weekStart),
|
key: ValueKey(weekStart),
|
||||||
controller: eventController,
|
controller: eventController,
|
||||||
@ -64,84 +112,19 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
height: 0,
|
height: 0,
|
||||||
),
|
),
|
||||||
weekDayBuilder: (date) {
|
weekDayBuilder: (date) {
|
||||||
final weekDays = _getWeekDays(weekStart);
|
return WeekDayHeader(
|
||||||
final selectedDayIndex =
|
date: date,
|
||||||
weekDays.indexWhere((d) => isSameDay(d, selectedDate));
|
isSelectedDay: isSameDay(date, selectedDate),
|
||||||
final index = weekDays.indexWhere((d) => isSameDay(d, date));
|
|
||||||
final isSelectedDay = index == selectedDayIndex;
|
|
||||||
final isToday = isSameDay(date, DateTime.now());
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat('EEE').format(date).toUpperCase(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 14,
|
|
||||||
color: isSelectedDay ? Colors.blue : Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
DateFormat('d').format(date),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 20,
|
|
||||||
color: isSelectedDay
|
|
||||||
? ColorsManager.blue1
|
|
||||||
: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
timeLineBuilder: (date) {
|
timeLineBuilder: (date) {
|
||||||
int hour = date.hour == 0
|
return TimeLineWidget(date: date);
|
||||||
? 12
|
|
||||||
: (date.hour > 12 ? date.hour - 12 : date.hour);
|
|
||||||
String period = date.hour >= 12 ? 'PM' : 'AM';
|
|
||||||
return Container(
|
|
||||||
height: 60,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '$hour',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
fontSize: 24,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
WidgetSpan(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 2, top: 6),
|
|
||||||
child: Text(
|
|
||||||
period,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
letterSpacing: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: PlaceholderAlignment.baseline,
|
|
||||||
baseline: TextBaseline.alphabetic,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
timeLineWidth: timeLineWidth,
|
timeLineWidth: timeLineWidth,
|
||||||
weekPageHeaderBuilder: (start, end) => Container(),
|
weekPageHeaderBuilder: (start, end) => Container(),
|
||||||
weekTitleHeight: 60,
|
weekTitleHeight: 60,
|
||||||
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
weekNumberBuilder: (firstDayOfWeek) => Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(right: 15, bottom: 10),
|
||||||
right: 15,
|
|
||||||
bottom: 10,
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
@ -158,49 +141,8 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventTileBuilder: (date, events, boundary, start, end) {
|
eventTileBuilder: (date, events, boundary, start, end) {
|
||||||
return Container(
|
return EventTileWidget(
|
||||||
margin:
|
events: events,
|
||||||
const EdgeInsets.symmetric(vertical: 2, horizontal: 2),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: events.map((event) {
|
|
||||||
final bool isEventEnded = event.endTime != null &&
|
|
||||||
event.endTime!.isBefore(DateTime.now());
|
|
||||||
return Expanded(
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isEventEnded
|
|
||||||
? ColorsManager.lightGrayBorderColor
|
|
||||||
: ColorsManager.blue1.withOpacity(0.25),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateFormat('h:mm a').format(event.startTime!),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.black87,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
event.title,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: ColorsManager.blackColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -219,6 +161,22 @@ class WeeklyCalendarPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (selectedSidebarIndex >= 0 &&
|
||||||
|
selectedSidebarIndex != selectedDayIndex)
|
||||||
|
Positioned(
|
||||||
|
left: (timeLineWidth + 3) +
|
||||||
|
(dayColumnWidth - 8) * (selectedSidebarIndex - 0.01),
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
width: dayColumnWidth,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 0, horizontal: 4),
|
||||||
|
color: Colors.orange.withOpacity(0.14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 50,
|
top: 50,
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/booking_system/presentation/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,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.blueGrey[100],
|
||||||
|
child: const Center(
|
||||||
|
child: Text(
|
||||||
|
'Side bar',
|
||||||
|
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: SizedBox(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SvgTextButton(
|
||||||
|
svgAsset: Assets.homeIcon,
|
||||||
|
label: 'Manage Bookable Spaces',
|
||||||
|
onPressed: () {
|
||||||
|
pageController.jumpToPage(2);
|
||||||
|
}),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
SvgTextButton(
|
||||||
|
svgAsset: Assets.groupIcon,
|
||||||
|
label: 'Manage Users',
|
||||||
|
onPressed: () {})
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
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/params/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
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,
|
||||||
|
bookingStartTime: parseTimeOfDay(json['startTime'] as String),
|
||||||
|
bookingEndTime: 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
class BookableSpacesParams {
|
||||||
|
int currentPage;
|
||||||
|
BookableSpacesParams({
|
||||||
|
required this.currentPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'page': currentPage,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
class NonBookableSpacesParams {
|
||||||
|
int currentPage;
|
||||||
|
String? searchedWords;
|
||||||
|
NonBookableSpacesParams({
|
||||||
|
required this.currentPage,
|
||||||
|
this.searchedWords,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'page': currentPage,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
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 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,
|
||||||
|
});
|
||||||
|
factory UpdateBookableSpaceParam.fromBookableModel(
|
||||||
|
BookableSpacemodel bookableSpace) {
|
||||||
|
return UpdateBookableSpaceParam(
|
||||||
|
spaceUuid: bookableSpace.spaceUuid,
|
||||||
|
availability: bookableSpace.spaceConfig!.availability,
|
||||||
|
bookableDays: bookableSpace.spaceConfig!.bookableDays,
|
||||||
|
cost: bookableSpace.spaceConfig!.cost,
|
||||||
|
bookingStartTime: formatTimeOfDayTo24HourString(
|
||||||
|
bookableSpace.spaceConfig!.bookingStartTime!),
|
||||||
|
bookingEndTime: formatTimeOfDayTo24HourString(
|
||||||
|
bookableSpace.spaceConfig!.bookingEndTime!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
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);
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
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/params/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);
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
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);
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
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 {}
|
@ -0,0 +1,18 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
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 {}
|
@ -0,0 +1,150 @@
|
|||||||
|
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/params/send_bookable_spaces_to_api_params.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.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<CallInitStateEvent>(_onCallInitStateEvent);
|
||||||
|
on<LoadUnBookableSpacesEvent>(_onLoadUnBookableSpacesEvent);
|
||||||
|
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
|
||||||
|
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
|
||||||
|
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
|
||||||
|
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
|
||||||
|
on<EditModeSelected>(_onEditModeSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeOfDay? get endTime =>
|
||||||
|
selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
|
||||||
|
|
||||||
|
TimeOfDay? get startTime =>
|
||||||
|
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
|
||||||
|
|
||||||
|
void _onCallInitStateEvent(
|
||||||
|
CallInitStateEvent event, Emitter<NonBookableSpacesState> emit) {
|
||||||
|
emit(NonBookableSpacesInitial());
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
add(CallInitStateEvent());
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
NonBookableSpacesError(e.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onCheckConfigurValidityEvent(
|
||||||
|
CheckConfigurValidityEvent event, Emitter<NonBookableSpacesState> emit) {
|
||||||
|
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
|
||||||
|
emit(ValidSaveButtonState());
|
||||||
|
} else {
|
||||||
|
emit(UnValidSaveButtonState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onEditModeSelected(
|
||||||
|
EditModeSelected event, Emitter<NonBookableSpacesState> emit) {
|
||||||
|
selectedBookableSpaces.clear();
|
||||||
|
selectedBookableSpaces.add(event.editingBookableSpace);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
part of 'non_bookaable_spaces_bloc.dart';
|
||||||
|
|
||||||
|
sealed class NonBookableSpacesEvent extends Equatable {
|
||||||
|
const NonBookableSpacesEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallInitStateEvent extends NonBookableSpacesEvent {}
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
class EditModeSelected extends NonBookableSpacesEvent {
|
||||||
|
final BookableSpacemodel editingBookableSpace;
|
||||||
|
const EditModeSelected({
|
||||||
|
required this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
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 {}
|
@ -0,0 +1,22 @@
|
|||||||
|
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 editValueInit() {
|
||||||
|
emit(StepTwoState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void goToNextStep() {
|
||||||
|
if (state is StepOneState) {
|
||||||
|
emit(StepTwoState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
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 {}
|
||||||
|
|
@ -0,0 +1,36 @@
|
|||||||
|
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));
|
||||||
|
event.onSuccess?.call();
|
||||||
|
} on APIException catch (e) {
|
||||||
|
emit(UpdateBookableSpaceFailure(error: e.message));
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
UpdateBookableSpaceFailure(error: e.toString()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
part of 'update_bookable_spaces_bloc.dart';
|
||||||
|
|
||||||
|
sealed class UpdateBookableSpaceEvent extends Equatable {
|
||||||
|
const UpdateBookableSpaceEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateBookableSpace extends UpdateBookableSpaceEvent {
|
||||||
|
final void Function()? onSuccess;
|
||||||
|
final UpdateBookableSpaceParam updatedParams;
|
||||||
|
const UpdateBookableSpace({
|
||||||
|
required this.updatedParams,
|
||||||
|
this.onSuccess,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
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/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/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();
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
SetupBookableSpacesDialog({
|
||||||
|
super.key,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<StepsCubit>(
|
||||||
|
create: editingBookableSpace == null
|
||||||
|
? (context) => StepsCubit()..initDialogValue()
|
||||||
|
: (context) => StepsCubit()..editValueInit(),
|
||||||
|
),
|
||||||
|
BlocProvider<NonBookableSpacesBloc>(
|
||||||
|
create: editingBookableSpace == null
|
||||||
|
? (context) => NonBookableSpacesBloc(
|
||||||
|
RemoteNonBookableSpaces(HTTPService()),
|
||||||
|
)..add(
|
||||||
|
LoadUnBookableSpacesEvent(
|
||||||
|
nonBookableSpacesParams:
|
||||||
|
NonBookableSpacesParams(currentPage: 1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: (context) => NonBookableSpacesBloc(
|
||||||
|
RemoteNonBookableSpaces(HTTPService()),
|
||||||
|
)..add(
|
||||||
|
EditModeSelected(
|
||||||
|
editingBookableSpace: editingBookableSpace!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
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,
|
||||||
|
editingBookableSpace: editingBookableSpace,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
isEditingMode: editingBookableSpace != null,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
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/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 {
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
const BookingPeriodWidget({
|
||||||
|
super.key,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
|
||||||
|
@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: editingBookableSpace == null
|
||||||
|
? 'Start Time'
|
||||||
|
: editingBookableSpace!.spaceConfig!.bookingStartTime!
|
||||||
|
.format(context),
|
||||||
|
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: editingBookableSpace == null
|
||||||
|
? 'End Time'
|
||||||
|
: editingBookableSpace!.spaceConfig!.bookingEndTime!
|
||||||
|
.format(context),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
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/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(
|
||||||
|
'Operation Done 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
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)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
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/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;
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
const DetailsStepsWidget({
|
||||||
|
super.key,
|
||||||
|
required this.pointsController,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
|
||||||
|
@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,
|
||||||
|
editingBookableSpace:editingBookableSpace
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
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/data/remote_update_bookable_space_service.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/screens/setup_bookable_spaces_dialog.dart';
|
||||||
|
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.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: () {
|
||||||
|
final bookableBloc = context.read<BookableSpacesBloc>();
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider.value(
|
||||||
|
value: bookableBloc,
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => UpdateBookableSpacesBloc(
|
||||||
|
RemoteUpdateBookableSpaceService(HTTPService()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: SetupBookableSpacesDialog(
|
||||||
|
editingBookableSpace: space,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
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/presentation/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(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
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/domain/models/bookable_space_model.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 StatefulWidget {
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
const PointsPartWidget({
|
||||||
|
super.key,
|
||||||
|
required this.pointsController,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
|
||||||
|
final TextEditingController pointsController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PointsPartWidget> createState() => _PointsPartWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PointsPartWidgetState extends State<PointsPartWidget> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
if (widget.editingBookableSpace != null) {
|
||||||
|
widget.pointsController.text =
|
||||||
|
widget.editingBookableSpace!.spaceConfig!.cost.toString();
|
||||||
|
}
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
widget.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(
|
||||||
|
widget.pointsController.text.isEmpty
|
||||||
|
? '0'
|
||||||
|
: widget.pointsController.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
context
|
||||||
|
.read<NonBookableSpacesBloc>()
|
||||||
|
.add(CheckConfigurValidityEvent());
|
||||||
|
},
|
||||||
|
controller: widget.pointsController,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
suffix: const SizedBox(),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
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/domain/params/update_bookable_space_param.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/update_bookable_spaces/update_bookable_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;
|
||||||
|
final bool isEditingMode;
|
||||||
|
|
||||||
|
const SaveSecondStepButton({
|
||||||
|
super.key,
|
||||||
|
required this.selectedSpaces,
|
||||||
|
required this.pointsController,
|
||||||
|
required this.isEditingMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@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,
|
||||||
|
)) {
|
||||||
|
isEditingMode
|
||||||
|
? callEditLogic(context)
|
||||||
|
: context.read<NonBookableSpacesBloc>().add(
|
||||||
|
SendBookableSpacesToApi(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancelPressed: () => context.pop(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void callEditLogic(BuildContext context) {
|
||||||
|
context.read<UpdateBookableSpacesBloc>().add(
|
||||||
|
UpdateBookableSpace(
|
||||||
|
onSuccess: () =>
|
||||||
|
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()),
|
||||||
|
updatedParams: UpdateBookableSpaceParam.fromBookableModel(
|
||||||
|
context
|
||||||
|
.read<NonBookableSpacesBloc>()
|
||||||
|
.selectedBookableSpaces
|
||||||
|
.first,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
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/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;
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
const StepTwoDetailsWidget({
|
||||||
|
super.key,
|
||||||
|
required this.pointsController,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 450,
|
||||||
|
height: 480,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
WeekDaysCheckboxRow(
|
||||||
|
editingBookableSpace: editingBookableSpace,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
BookingPeriodWidget(
|
||||||
|
editingBookableSpace: editingBookableSpace,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: editingBookableSpace == null
|
||||||
|
? (context) => TogglePointsSwitchCubit()..activateSwitch()
|
||||||
|
: editingBookableSpace!.spaceConfig!.cost == 0
|
||||||
|
? (context) => TogglePointsSwitchCubit()..unActivateSwitch()
|
||||||
|
: (context) => TogglePointsSwitchCubit()..activateSwitch(),
|
||||||
|
child: PointsPartWidget(
|
||||||
|
pointsController: pointsController,
|
||||||
|
editingBookableSpace: editingBookableSpace),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
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 {
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/utils/color_manager.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
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()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
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 WeekDaysCheckboxRow extends StatefulWidget {
|
||||||
|
final BookableSpacemodel? editingBookableSpace;
|
||||||
|
const WeekDaysCheckboxRow({
|
||||||
|
super.key,
|
||||||
|
this.editingBookableSpace,
|
||||||
|
});
|
||||||
|
|
||||||
|
@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
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
final existingDays =
|
||||||
|
widget.editingBookableSpace?.spaceConfig?.bookableDays ?? [];
|
||||||
|
|
||||||
|
for (var day in _daysChecked.keys) {
|
||||||
|
if (existingDays.contains(day)) {
|
||||||
|
_daysChecked[day] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
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_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
import 'package:syncrow_web/pages/access_management/bloc/access_event.dart';
|
||||||
import 'package:syncrow_web/pages/access_management/booking_system/presentation/view/booking_page.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/booking_system/presentation/view/booking_page.dart' hide BookingPage;
|
||||||
import 'package:syncrow_web/pages/access_management/view/access_overview_content.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/pages/device_managment/shared/navigate_home_grid_view.dart';
|
||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
@ -71,9 +73,14 @@ class _AccessManagementPageState extends State<AccessManagementPage>
|
|||||||
scaffoldBody: PageView(
|
scaffoldBody: PageView(
|
||||||
controller: _pageController,
|
controller: _pageController,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
children: const [
|
children: [
|
||||||
AccessOverviewContent(),
|
const AccessOverviewContent(),
|
||||||
BookingPage(),
|
BookingPage(
|
||||||
|
pageController: _pageController,
|
||||||
|
),
|
||||||
|
ManageBookableSpacesPage(
|
||||||
|
pageController: _pageController,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -132,6 +132,8 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: _horizontalScrollController,
|
controller: _horizontalScrollController,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
|
physics:
|
||||||
|
widget.isEmpty ? const NeverScrollableScrollPhysics() : null,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: _totalTableWidth,
|
width: _totalTableWidth,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -164,7 +166,6 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: widget.isEmpty
|
child: widget.isEmpty
|
||||||
? _buildEmptyState()
|
? _buildEmptyState()
|
||||||
@ -265,7 +266,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: widget.size.height * 0.5),
|
SizedBox(height: widget.size.height * 0.2),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -46,15 +46,15 @@ class DeviceManagementBloc
|
|||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
if (spaceBloc.state.selectedCommunities.isEmpty) {
|
if (spaceBloc.state.selectedCommunities.isEmpty) {
|
||||||
devices = await DevicesManagementApi().fetchDevices('', '', projectUuid);
|
devices = await DevicesManagementApi().fetchDevices(
|
||||||
|
projectUuid,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
for (final community in spaceBloc.state.selectedCommunities) {
|
for (var community in spaceBloc.state.selectedCommunities) {
|
||||||
final spacesList =
|
final spacesList =
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
spaceBloc.state.selectedCommunityAndSpaces[community] ?? [];
|
||||||
for (final space in spacesList) {
|
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(await DevicesManagementApi()
|
||||||
.fetchDevices(community, space, projectUuid));
|
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ class DeviceManagementPage extends StatefulWidget with HelperResponsiveLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
context.read<SpaceTreeBloc>().add(InitialEvent());
|
context.read<SpaceTreeBloc>().add(InitialEvent());
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@ -90,7 +90,7 @@ class _DeviceManagementPageState extends State<DeviceManagementPage> {
|
|||||||
const TriggerSwitchTabsEvent(isRoutineTab: true));
|
const TriggerSwitchTabsEvent(isRoutineTab: true));
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'Routines',
|
'Workflow Automation',
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
color: state.routineTab
|
color: state.routineTab
|
||||||
? ColorsManager.whiteColors
|
? ColorsManager.whiteColors
|
||||||
|
@ -29,7 +29,9 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
borderRadius: 8,
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
backgroundColor: ColorsManager.boxColor,
|
backgroundColor: ColorsManager.boxColor,
|
||||||
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
child: Text('Cancel', style: context.textTheme.bodyMedium),
|
||||||
@ -39,6 +41,8 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: isActive
|
child: isActive
|
||||||
? DefaultButton(
|
? DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -49,10 +53,12 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: Colors.red,
|
backgroundColor: ColorsManager.red100,
|
||||||
child: const Text('Stop'),
|
child: const Text('Stop'),
|
||||||
)
|
)
|
||||||
: DefaultButton(
|
: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.read<ScheduleBloc>().add(
|
context.read<ScheduleBloc>().add(
|
||||||
@ -63,7 +69,7 @@ class CountdownModeButtons extends StatelessWidget {
|
|||||||
countDownCode: countDownCode),
|
countDownCode: countDownCode),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -226,6 +226,7 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
index.toString().padLeft(2, '0'),
|
index.toString().padLeft(2, '0'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
color: isActive ? ColorsManager.grayColor : Colors.black,
|
color: isActive ? ColorsManager.grayColor : Colors.black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -240,7 +241,8 @@ class _CountdownInchingViewState extends State<CountdownInchingView> {
|
|||||||
label,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: ColorsManager.grayColor,
|
color: ColorsManager.grayColor,
|
||||||
fontSize: 18,
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -31,12 +31,11 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => ScheduleBloc(
|
create: (_) => ScheduleBloc(deviceId: deviceUuid,)
|
||||||
deviceId: deviceUuid,
|
|
||||||
)
|
|
||||||
..add(ScheduleGetEvent(category: category))
|
..add(ScheduleGetEvent(category: category))
|
||||||
..add(ScheduleFetchStatusEvent(
|
..add(ScheduleFetchStatusEvent(
|
||||||
deviceId: deviceUuid, countdownCode: countdownCode ?? '')),
|
deviceId: deviceUuid,
|
||||||
|
countdownCode: countdownCode ?? '')),
|
||||||
child: Dialog(
|
child: Dialog(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
insetPadding: const EdgeInsets.all(20),
|
insetPadding: const EdgeInsets.all(20),
|
||||||
@ -77,7 +76,8 @@ class BuildScheduleView extends StatelessWidget {
|
|||||||
category: category,
|
category: category,
|
||||||
time: '',
|
time: '',
|
||||||
function: Status(
|
function: Status(
|
||||||
code: code.toString(), value: null),
|
code: code.toString(),
|
||||||
|
value: true),
|
||||||
days: [],
|
days: [],
|
||||||
),
|
),
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
@ -13,9 +13,9 @@ class ScheduleHeader extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'Scheduling',
|
'Scheduling',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
fontSize: 22,
|
fontWeight: FontWeight.w700,
|
||||||
color: ColorsManager.dialogBlueTitle,
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -27,7 +27,7 @@ class ScheduleManagementUI extends StatelessWidget {
|
|||||||
width: 170,
|
width: 170,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
borderColor: ColorsManager.boxColor,
|
borderColor: ColorsManager.grayColor.withOpacity(0.5),
|
||||||
padding: 2,
|
padding: 2,
|
||||||
backgroundColor: ColorsManager.graysColor,
|
backgroundColor: ColorsManager.graysColor,
|
||||||
borderRadius: 15,
|
borderRadius: 15,
|
||||||
|
@ -19,6 +19,8 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -33,9 +35,11 @@ class ScheduleModeButtons extends StatelessWidget {
|
|||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DefaultButton(
|
child: DefaultButton(
|
||||||
|
elevation: 2.5,
|
||||||
|
borderRadius: 8,
|
||||||
height: 40,
|
height: 40,
|
||||||
onPressed: onSave,
|
onPressed: onSave,
|
||||||
backgroundColor: ColorsManager.primaryColor,
|
backgroundColor: ColorsManager.primaryColorWithOpacity,
|
||||||
child: const Text('Save'),
|
child: const Text('Save'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -35,12 +35,12 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
children: [
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
context, 'Countdown', ScheduleModes.countdown, currentMode),
|
||||||
_buildRadioTile(
|
_buildRadioTile(
|
||||||
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
context, 'Schedule', ScheduleModes.schedule, currentMode),
|
||||||
|
const Spacer(flex: 1),
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
// context, 'Circulate', ScheduleModes.circulate, currentMode),
|
||||||
// _buildRadioTile(
|
// _buildRadioTile(
|
||||||
@ -65,6 +65,7 @@ class ScheduleModeSelector extends StatelessWidget {
|
|||||||
style: context.textTheme.bodySmall!.copyWith(
|
style: context.textTheme.bodySmall!.copyWith(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
color: ColorsManager.blackColor,
|
color: ColorsManager.blackColor,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: Radio<ScheduleModes>(
|
leading: Radio<ScheduleModes>(
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
|
||||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
|
||||||
class ScheduleDialogHelper {
|
class ScheduleDialogHelper {
|
||||||
static const List<String> allDays = [
|
static const List<String> allDays = [
|
||||||
@ -56,8 +58,9 @@ class ScheduleDialogHelper {
|
|||||||
Text(
|
Text(
|
||||||
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
isEdit ? 'Edit Schedule' : 'Add Schedule',
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Colors.blue,
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(),
|
const SizedBox(),
|
||||||
@ -69,9 +72,9 @@ class ScheduleDialogHelper {
|
|||||||
height: 40,
|
height: 40,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[200],
|
backgroundColor: ColorsManager.boxColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(15),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -110,19 +113,8 @@ class ScheduleDialogHelper {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
ScheduleModeButtons(
|
||||||
width: 100,
|
onSave: () {
|
||||||
child: OutlinedButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(ctx, null);
|
|
||||||
},
|
|
||||||
child: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 100,
|
|
||||||
child: ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
dynamic temp;
|
dynamic temp;
|
||||||
if (deviceType == 'CUR_2') {
|
if (deviceType == 'CUR_2') {
|
||||||
temp = functionOn! ? 'open' : 'close';
|
temp = functionOn! ? 'open' : 'close';
|
||||||
@ -141,8 +133,7 @@ class ScheduleDialogHelper {
|
|||||||
);
|
);
|
||||||
Navigator.pop(ctx, entry);
|
Navigator.pop(ctx, entry);
|
||||||
},
|
},
|
||||||
child: const Text('Save'),
|
),
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -153,6 +153,7 @@ class EditUserModel {
|
|||||||
final String? jobTitle; // can be empty
|
final String? jobTitle; // can be empty
|
||||||
final String roleType; // e.g. "ADMIN"
|
final String roleType; // e.g. "ADMIN"
|
||||||
final List<UserSpaceModel> spaces;
|
final List<UserSpaceModel> spaces;
|
||||||
|
final String? companyName;
|
||||||
|
|
||||||
EditUserModel({
|
EditUserModel({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
@ -167,6 +168,7 @@ class EditUserModel {
|
|||||||
required this.jobTitle,
|
required this.jobTitle,
|
||||||
required this.roleType,
|
required this.roleType,
|
||||||
required this.spaces,
|
required this.spaces,
|
||||||
|
this.companyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Create a [UserData] from JSON data
|
/// Create a [UserData] from JSON data
|
||||||
@ -182,6 +184,7 @@ class EditUserModel {
|
|||||||
invitedBy: json['invitedBy'] as String,
|
invitedBy: json['invitedBy'] as String,
|
||||||
phoneNumber: json['phoneNumber'] ?? '',
|
phoneNumber: json['phoneNumber'] ?? '',
|
||||||
jobTitle: json['jobTitle'] ?? '',
|
jobTitle: json['jobTitle'] ?? '',
|
||||||
|
companyName: json['companyName'] as String?,
|
||||||
roleType: json['roleType'] as String,
|
roleType: json['roleType'] as String,
|
||||||
spaces: (json['spaces'] as List<dynamic>)
|
spaces: (json['spaces'] as List<dynamic>)
|
||||||
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
|
.map((e) => UserSpaceModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
@ -12,7 +12,7 @@ class RolesUserModel {
|
|||||||
final dynamic jobTitle;
|
final dynamic jobTitle;
|
||||||
final dynamic createdDate;
|
final dynamic createdDate;
|
||||||
final dynamic createdTime;
|
final dynamic createdTime;
|
||||||
|
final String? companyName;
|
||||||
RolesUserModel({
|
RolesUserModel({
|
||||||
required this.uuid,
|
required this.uuid,
|
||||||
required this.createdAt,
|
required this.createdAt,
|
||||||
@ -27,6 +27,7 @@ class RolesUserModel {
|
|||||||
this.jobTitle,
|
this.jobTitle,
|
||||||
required this.createdDate,
|
required this.createdDate,
|
||||||
required this.createdTime,
|
required this.createdTime,
|
||||||
|
this.companyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory RolesUserModel.fromJson(Map<String, dynamic> json) {
|
factory RolesUserModel.fromJson(Map<String, dynamic> json) {
|
||||||
@ -47,6 +48,7 @@ class RolesUserModel {
|
|||||||
: json['jobTitle'],
|
: json['jobTitle'],
|
||||||
createdDate: json['createdDate'],
|
createdDate: json['createdDate'],
|
||||||
createdTime: json['createdTime'],
|
createdTime: json['createdTime'],
|
||||||
|
companyName: json['companyName'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
final TextEditingController lastNameController = TextEditingController();
|
final TextEditingController lastNameController = TextEditingController();
|
||||||
final TextEditingController emailController = TextEditingController();
|
final TextEditingController emailController = TextEditingController();
|
||||||
final TextEditingController phoneController = TextEditingController();
|
final TextEditingController phoneController = TextEditingController();
|
||||||
final TextEditingController jobTitleController = TextEditingController();
|
final TextEditingController companyNameController = TextEditingController();
|
||||||
final TextEditingController roleSearchController = TextEditingController();
|
final TextEditingController roleSearchController = TextEditingController();
|
||||||
|
|
||||||
bool? isCompleteBasics;
|
bool? isCompleteBasics;
|
||||||
@ -352,7 +352,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
bool res = await UserPermissionApi().sendInviteUser(
|
bool res = await UserPermissionApi().sendInviteUser(
|
||||||
email: emailController.text,
|
email: emailController.text,
|
||||||
firstName: firstNameController.text,
|
firstName: firstNameController.text,
|
||||||
jobTitle: jobTitleController.text,
|
companyName: companyNameController.text,
|
||||||
lastName: lastNameController.text,
|
lastName: lastNameController.text,
|
||||||
phoneNumber: phoneController.text,
|
phoneNumber: phoneController.text,
|
||||||
roleUuid: roleSelected,
|
roleUuid: roleSelected,
|
||||||
@ -405,7 +405,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
bool res = await UserPermissionApi().editInviteUser(
|
bool res = await UserPermissionApi().editInviteUser(
|
||||||
userId: event.userId,
|
userId: event.userId,
|
||||||
firstName: firstNameController.text,
|
firstName: firstNameController.text,
|
||||||
jobTitle: jobTitleController.text,
|
companyName: companyNameController.text,
|
||||||
lastName: lastNameController.text,
|
lastName: lastNameController.text,
|
||||||
phoneNumber: phoneController.text,
|
phoneNumber: phoneController.text,
|
||||||
roleUuid: roleSelected,
|
roleUuid: roleSelected,
|
||||||
@ -529,7 +529,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
lastNameController.text = res.lastName;
|
lastNameController.text = res.lastName;
|
||||||
emailController.text = res.email;
|
emailController.text = res.email;
|
||||||
phoneController.text = res.phoneNumber ?? '';
|
phoneController.text = res.phoneNumber ?? '';
|
||||||
jobTitleController.text = res.jobTitle ?? '';
|
companyNameController.text = res.companyName ?? '';
|
||||||
res.roleType;
|
res.roleType;
|
||||||
res.spaces.map((space) {
|
res.spaces.map((space) {
|
||||||
selectedIds.add(space.uuid);
|
selectedIds.add(space.uuid);
|
||||||
@ -645,7 +645,7 @@ class UsersBloc extends Bloc<UsersEvent, UsersState> {
|
|||||||
lastNameController.dispose();
|
lastNameController.dispose();
|
||||||
emailController.dispose();
|
emailController.dispose();
|
||||||
phoneController.dispose();
|
phoneController.dispose();
|
||||||
jobTitleController.dispose();
|
companyNameController.dispose();
|
||||||
roleSearchController.dispose();
|
roleSearchController.dispose();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
@ -317,7 +317,7 @@ class BasicsView extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Job Title',
|
'Company Name',
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@ -328,11 +328,11 @@ class BasicsView extends StatelessWidget {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: _blocRole.jobTitleController,
|
controller: _blocRole.companyNameController,
|
||||||
style:
|
style:
|
||||||
const TextStyle(color: ColorsManager.blackColor),
|
const TextStyle(color: ColorsManager.blackColor),
|
||||||
decoration: inputTextFormDeco(
|
decoration: inputTextFormDeco(
|
||||||
hintText: "Job Title (Optional)")
|
hintText: 'Company Name (Optional)')
|
||||||
.copyWith(
|
.copyWith(
|
||||||
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
hintStyle: context.textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
|
@ -411,7 +411,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
titles: const [
|
titles: const [
|
||||||
"Full Name",
|
"Full Name",
|
||||||
"Email Address",
|
"Email Address",
|
||||||
"Job Title",
|
"Company Name",
|
||||||
"Role",
|
"Role",
|
||||||
"Creation Date",
|
"Creation Date",
|
||||||
"Creation Time",
|
"Creation Time",
|
||||||
@ -424,7 +424,7 @@ class UsersPage extends StatelessWidget {
|
|||||||
return [
|
return [
|
||||||
Text('${user.firstName} ${user.lastName}'),
|
Text('${user.firstName} ${user.lastName}'),
|
||||||
Text(user.email),
|
Text(user.email),
|
||||||
Text(user.jobTitle),
|
Center(child: Text(user.companyName ?? '-')),
|
||||||
Text(user.roleType ?? ''),
|
Text(user.roleType ?? ''),
|
||||||
Text(user.createdDate ?? ''),
|
Text(user.createdDate ?? ''),
|
||||||
Text(user.createdTime ?? ''),
|
Text(user.createdTime ?? ''),
|
||||||
|
@ -936,16 +936,15 @@ Future<void> _onLoadScenes(
|
|||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList =
|
List<String> spacesList =
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(await DevicesManagementApi()
|
||||||
.fetchDevices(communityId, spaceId, projectUuid));
|
.fetchDevices(projectUuid, spacesId: spacesList));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
createRoutineBloc.selectedCommunityId,
|
projectUuid,
|
||||||
createRoutineBloc.selectedSpaceId,
|
spacesId: [createRoutineBloc.selectedSpaceId],
|
||||||
projectUuid));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isLoading: false, devices: devices));
|
emit(state.copyWith(isLoading: false, devices: devices));
|
||||||
|
@ -96,9 +96,7 @@ class _WallPresenceSensorState extends State<FlushPresenceSensor> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
DialogHeader(widget.dialogType == 'THEN'
|
const DialogHeader('Presence Sensor'),
|
||||||
? 'Presence Sensor Functions'
|
|
||||||
: 'Presence Sensor Condition'),
|
|
||||||
Expanded(child: _buildMainContent(context, state)),
|
Expanded(child: _buildMainContent(context, state)),
|
||||||
_buildDialogFooter(context, state),
|
_buildDialogFooter(context, state),
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||||
|
|
||||||
|
class SpaceReorderDataModel {
|
||||||
|
const SpaceReorderDataModel({
|
||||||
|
required this.space,
|
||||||
|
this.parent,
|
||||||
|
this.community,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SpaceModel space;
|
||||||
|
final SpaceModel? parent;
|
||||||
|
final CommunityModel? community;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user