loading indicater has been added

This commit is contained in:
Daniah Ayad Al-sultani
2026-02-17 17:19:53 +03:00
parent 5117fbdabe
commit 3a9e7ca8db
2 changed files with 260 additions and 237 deletions

View File

@@ -51,19 +51,24 @@ class _FinanceScreenState extends State<FinanceScreen> {
void _triggerLoad() { void _triggerLoad() {
if (_employeeId != null && _employeeId!.isNotEmpty) { if (_employeeId != null && _employeeId!.isNotEmpty) {
if (_financeBloc.state is FinanceInitial) { _financeBloc.add(
_financeBloc.add( LoadFinanceDataEvent(
LoadFinanceDataEvent( employeeId: _employeeId!,
employeeId: _employeeId!, category: currentCategory,
category: currentCategory, month: selectedDate.month,
month: selectedDate.month, year: selectedDate.year,
year: selectedDate.year, ),
), );
);
}
} }
} }
Future<void> _onRefresh() async {
_triggerLoad();
await _financeBloc.stream.firstWhere(
(state) => state is FinanceLoaded || state is FinanceError,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
@@ -71,145 +76,153 @@ class _FinanceScreenState extends State<FinanceScreen> {
child: Directionality( child: Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: SafeArea( child: SafeArea(
child: CustomScrollView( child: RefreshIndicator(
controller: scrollController, onRefresh: _onRefresh,
physics: const BouncingScrollPhysics(), color: const Color(0xFF0A6B4A),
slivers: [ child: CustomScrollView(
SliverToBoxAdapter( controller: scrollController,
child: SettingsBar( physics: const AlwaysScrollableScrollPhysics(
selectedIndex: 0, parent: BouncingScrollPhysics(),
showBackButton: false,
iconPaths: const [
'assets/images/user.svg',
'assets/images/ball.svg',
],
onTap: (index) {
if (index == 0) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UserSettingsScreen(),
),
);
} else if (index == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NotificationsScreen(),
),
);
}
},
),
), ),
const SliverToBoxAdapter(child: SizedBox(height: 5)), slivers: [
SliverToBoxAdapter(
/// SUMMARY CARD child: SettingsBar(
SliverToBoxAdapter( selectedIndex: 0,
child: BlocBuilder<FinanceBloc, FinanceState>( showBackButton: false,
buildWhen: (previous, current) => current is FinanceLoaded, iconPaths: const [
builder: (context, state) { 'assets/images/user.svg',
String amount = "0"; 'assets/images/ball.svg',
if (state is FinanceLoaded) { ],
amount = state.netSalary.toStringAsFixed(0); onTap: (index) {
amount = amount.replaceAllMapped( if (index == 0) {
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), Navigator.push(
(Match m) => '${m[1]},', context,
); MaterialPageRoute(
} builder: (context) => const UserSettingsScreen(),
),
return FinanceSummaryCard(
totalAmount: amount,
currentCategory: currentCategory,
onCalendarTap: () async {
final date = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
); );
if (date != null && mounted) { } else if (index == 1) {
setState(() => selectedDate = date); Navigator.push(
if (_employeeId != null && _employeeId!.isNotEmpty) { context,
_financeBloc.add( MaterialPageRoute(
LoadFinanceDataEvent( builder: (context) => const NotificationsScreen(),
employeeId: _employeeId!, ),
category: currentCategory, );
month: selectedDate.month, }
year: selectedDate.year, },
), ),
);
}
}
},
onCategoryChanged: (category) {
if (category != null) {
setState(() => currentCategory = category);
if (_employeeId != null && _employeeId!.isNotEmpty) {
_financeBloc.add(
LoadFinanceDataEvent(
employeeId: _employeeId!,
category: currentCategory,
month: selectedDate.month,
year: selectedDate.year,
),
);
}
}
},
);
},
), ),
), const SliverToBoxAdapter(child: SizedBox(height: 5)),
/// DATA LIST /// SUMMARY CARD
BlocBuilder<FinanceBloc, FinanceState>( SliverToBoxAdapter(
builder: (context, state) { child: BlocBuilder<FinanceBloc, FinanceState>(
if (state is FinanceLoading || state is FinanceInitial) { buildWhen: (previous, current) => current is FinanceLoaded,
return const SliverToBoxAdapter( builder: (context, state) {
child: Center( String amount = "0";
child: Padding( if (state is FinanceLoaded) {
padding: EdgeInsets.all(20.0), amount = state.netSalary.toStringAsFixed(0);
child: CircularProgressIndicator(), amount = amount.replaceAllMapped(
), RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
), (Match m) => '${m[1]},',
); );
} else if (state is FinanceLoaded) { }
if (state.records.isEmpty) {
return FinanceSummaryCard(
totalAmount: amount,
currentCategory: currentCategory,
onCalendarTap: () async {
final date = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2020),
lastDate: DateTime(2030),
);
if (date != null && mounted) {
setState(() => selectedDate = date);
if (_employeeId != null &&
_employeeId!.isNotEmpty) {
_financeBloc.add(
LoadFinanceDataEvent(
employeeId: _employeeId!,
category: currentCategory,
month: selectedDate.month,
year: selectedDate.year,
),
);
}
}
},
onCategoryChanged: (category) {
if (category != null) {
setState(() => currentCategory = category);
if (_employeeId != null &&
_employeeId!.isNotEmpty) {
_financeBloc.add(
LoadFinanceDataEvent(
employeeId: _employeeId!,
category: currentCategory,
month: selectedDate.month,
year: selectedDate.year,
),
);
}
}
},
);
},
),
),
/// DATA LIST
BlocBuilder<FinanceBloc, FinanceState>(
builder: (context, state) {
if (state is FinanceLoading || state is FinanceInitial) {
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(20.0), padding: EdgeInsets.all(20.0),
child: Text("لا توجد سجلات"), child: CircularProgressIndicator(),
),
),
);
} else if (state is FinanceLoaded) {
if (state.records.isEmpty) {
return const SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text("لا توجد سجلات"),
),
),
);
}
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return WorkDayCard(record: state.records[index]);
}, childCount: state.records.length),
);
} else if (state is FinanceError) {
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text(
state.message,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
), ),
), ),
); );
} }
return SliverList( return const SliverToBoxAdapter(child: SizedBox());
delegate: SliverChildBuilderDelegate((context, index) { },
return WorkDayCard(record: state.records[index]); ),
}, childCount: state.records.length),
);
} else if (state is FinanceError) {
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text(
state.message,
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
),
),
);
}
return const SliverToBoxAdapter(child: SizedBox());
},
),
const SliverToBoxAdapter(child: SizedBox(height: 120)), const SliverToBoxAdapter(child: SizedBox(height: 120)),
], ],
),
), ),
), ),
), ),

View File

@@ -223,6 +223,10 @@ class _HolidayScreenState extends State<HolidayScreen> {
); );
} }
Future<void> _onRefresh() async {
await Future.wait([_loadVacationsFromAPI(), _loadAdvancesFromAPI()]);
}
AdvanceRequest _convertAdvanceToAdvanceRequest(AdvanceDataModel advance) { AdvanceRequest _convertAdvanceToAdvanceRequest(AdvanceDataModel advance) {
// Convert state (0=waiting, 1=approved, 2=denied) to status string // Convert state (0=waiting, 1=approved, 2=denied) to status string
String status = "waiting"; String status = "waiting";
@@ -248,123 +252,129 @@ class _HolidayScreenState extends State<HolidayScreen> {
// ⭐ MAIN CONTENT - CUSTOM SCROLL VIEW // ⭐ MAIN CONTENT - CUSTOM SCROLL VIEW
// --------------------------------------------------------- // ---------------------------------------------------------
SafeArea( SafeArea(
child: CustomScrollView( child: RefreshIndicator(
controller: _scrollController, onRefresh: _onRefresh,
physics: const BouncingScrollPhysics(), color: const Color(0xFF0A6B4A),
slivers: [ child: CustomScrollView(
// SETTINGS BAR controller: _scrollController,
SliverToBoxAdapter( physics: const AlwaysScrollableScrollPhysics(
child: Padding( parent: BouncingScrollPhysics(),
padding: const EdgeInsets.only(top: 8), ),
// First, make sure you have your screens defined slivers: [
// SETTINGS BAR
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(top: 8),
// First, make sure you have your screens defined
// Then in your main widget: // Then in your main widget:
child: SettingsBar( child: SettingsBar(
selectedIndex: 0, selectedIndex: 0,
showBackButton: false, showBackButton: false,
iconPaths: [ iconPaths: [
'assets/images/user.svg', 'assets/images/user.svg',
'assets/images/ball.svg', 'assets/images/ball.svg',
], ],
onTap: (index) { onTap: (index) {
if (index == 0) { if (index == 0) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => UserSettingsScreen(), builder: (context) => UserSettingsScreen(),
), ),
); );
} else if (index == 1) { } else if (index == 1) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => NotificationsScreen(), builder: (context) => NotificationsScreen(),
), ),
); );
} }
}, },
),
), ),
), ),
),
const SliverToBoxAdapter(child: SizedBox(height: 5)), const SliverToBoxAdapter(child: SizedBox(height: 5)),
// TABS SECTION // TABS SECTION
SliverToBoxAdapter( SliverToBoxAdapter(
child: SizedBox( child: SizedBox(
height: 55, height: 55,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// الأجازات // الأجازات
GestureDetector( GestureDetector(
onTap: () => setState(() => activeTab = 1), onTap: () => setState(() => activeTab = 1),
child: Column( child: Column(
children: [ children: [
Text( Text(
"الأجازات", "الأجازات",
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: color:
activeTab == 1 activeTab == 1
? const Color(0xFF8EFDC2) ? const Color(0xFF8EFDC2)
: const Color(0x9EFFFFFF), : const Color(0x9EFFFFFF),
),
), ),
), if (activeTab == 1)
if (activeTab == 1) Container(
Container( width: 60,
width: 60, height: 2,
height: 2, margin: const EdgeInsets.only(top: 4),
margin: const EdgeInsets.only(top: 4), color: const Color(0xFF8EFDC2),
color: const Color(0xFF8EFDC2), ),
), ],
], ),
), ),
),
const SizedBox(width: 70), const SizedBox(width: 70),
// السلف // السلف
GestureDetector( GestureDetector(
onTap: () => setState(() => activeTab = 0), onTap: () => setState(() => activeTab = 0),
child: Column( child: Column(
children: [ children: [
Text( Text(
"السلف", "السلف",
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: color:
activeTab == 0 activeTab == 0
? const Color(0xFF8EFDC2) ? const Color(0xFF8EFDC2)
: const Color(0x9EFFFFFF), : const Color(0x9EFFFFFF),
),
), ),
), if (activeTab == 0)
if (activeTab == 0) Container(
Container( width: 60,
width: 60, height: 2,
height: 2, margin: const EdgeInsets.only(top: 4),
margin: const EdgeInsets.only(top: 4), color: const Color(0xFF8EFDC2),
color: const Color(0xFF8EFDC2), ),
), ],
], ),
), ),
), ],
], ),
), ),
), ),
),
const SliverToBoxAdapter(child: SizedBox(height: 20)), const SliverToBoxAdapter(child: SizedBox(height: 20)),
// CONTENT LISTS // CONTENT LISTS
activeTab == 1 activeTab == 1
? _buildLeaveRequestsSliver() ? _buildLeaveRequestsSliver()
: _buildAdvanceRequestsSliver(), : _buildAdvanceRequestsSliver(),
const SliverToBoxAdapter(child: SizedBox(height: 120)), const SliverToBoxAdapter(child: SizedBox(height: 120)),
], ],
),
), ),
), ),