feat: implement attire section toggles for required and non-essential items in AttirePage
This commit is contained in:
@@ -8,13 +8,20 @@ import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:staff_attire/src/presentation/blocs/attire/attire_cubit.dart';
|
||||
import 'package:staff_attire/src/presentation/blocs/attire/attire_state.dart';
|
||||
|
||||
import '../widgets/attire_filter_chips.dart';
|
||||
import '../widgets/attire_info_card.dart';
|
||||
import '../widgets/attire_item_card.dart';
|
||||
|
||||
class AttirePage extends StatelessWidget {
|
||||
class AttirePage extends StatefulWidget {
|
||||
const AttirePage({super.key});
|
||||
|
||||
@override
|
||||
State<AttirePage> createState() => _AttirePageState();
|
||||
}
|
||||
|
||||
class _AttirePageState extends State<AttirePage> {
|
||||
bool _showRequired = true;
|
||||
bool _showNonEssential = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final AttireCubit cubit = Modular.get<AttireCubit>();
|
||||
@@ -42,7 +49,12 @@ class AttirePage extends StatelessWidget {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
final List<AttireItem> filteredOptions = state.filteredOptions;
|
||||
final List<AttireItem> requiredItems = state.options
|
||||
.where((AttireItem item) => item.isMandatory)
|
||||
.toList();
|
||||
final List<AttireItem> nonEssentialItems = state.options
|
||||
.where((AttireItem item) => !item.isMandatory)
|
||||
.toList();
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
@@ -55,55 +67,110 @@ class AttirePage extends StatelessWidget {
|
||||
const AttireInfoCard(),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Filter Chips
|
||||
AttireFilterChips(
|
||||
selectedFilter: state.filter,
|
||||
onFilterChanged: cubit.updateFilter,
|
||||
// Section toggle chips
|
||||
Row(
|
||||
children: <Widget>[
|
||||
_SectionTab(
|
||||
label: 'Required',
|
||||
isSelected: _showRequired,
|
||||
onTap: () => setState(
|
||||
() => _showRequired = !_showRequired,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
_SectionTab(
|
||||
label: 'Non-Essential',
|
||||
isSelected: _showNonEssential,
|
||||
onTap: () => setState(
|
||||
() => _showNonEssential = !_showNonEssential,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Item List
|
||||
if (filteredOptions.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space10,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.shirt,
|
||||
size: 48,
|
||||
color: UiColors.iconInactive,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(
|
||||
context.t.staff_profile_attire.capture.no_items_filter,
|
||||
style: UiTypography.body1m.textSecondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
// Required section
|
||||
if (_showRequired) ...<Widget>[
|
||||
_SectionHeader(
|
||||
title: 'Required',
|
||||
count: requiredItems.length,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
if (requiredItems.isEmpty)
|
||||
_EmptySection(
|
||||
message: context
|
||||
.t
|
||||
.staff_profile_attire
|
||||
.capture
|
||||
.no_items_filter,
|
||||
)
|
||||
else
|
||||
...requiredItems.map((AttireItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: AttireItemCard(
|
||||
item: item,
|
||||
isUploading: false,
|
||||
uploadedPhotoUrl: state.photoUrls[item.id],
|
||||
onTap: () {
|
||||
Modular.to.toAttireCapture(
|
||||
item: item,
|
||||
initialPhotoUrl: state.photoUrls[item.id],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
||||
// Divider between sections
|
||||
if (_showRequired && _showNonEssential)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: UiConstants.space8,
|
||||
),
|
||||
child: Divider(),
|
||||
)
|
||||
else
|
||||
...filteredOptions.map((AttireItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: AttireItemCard(
|
||||
item: item,
|
||||
isUploading: false,
|
||||
uploadedPhotoUrl: state.photoUrls[item.id],
|
||||
onTap: () {
|
||||
Modular.to.toAttireCapture(
|
||||
item: item,
|
||||
initialPhotoUrl: state.photoUrls[item.id],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: UiConstants.space6),
|
||||
|
||||
// Non-Essential section
|
||||
if (_showNonEssential) ...<Widget>[
|
||||
_SectionHeader(
|
||||
title: 'Non-Essential',
|
||||
count: nonEssentialItems.length,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
if (nonEssentialItems.isEmpty)
|
||||
_EmptySection(
|
||||
message: context
|
||||
.t
|
||||
.staff_profile_attire
|
||||
.capture
|
||||
.no_items_filter,
|
||||
)
|
||||
else
|
||||
...nonEssentialItems.map((AttireItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: UiConstants.space3,
|
||||
),
|
||||
child: AttireItemCard(
|
||||
item: item,
|
||||
isUploading: false,
|
||||
uploadedPhotoUrl: state.photoUrls[item.id],
|
||||
onTap: () {
|
||||
Modular.to.toAttireCapture(
|
||||
item: item,
|
||||
initialPhotoUrl: state.photoUrls[item.id],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
const SizedBox(height: UiConstants.space20),
|
||||
],
|
||||
),
|
||||
@@ -117,3 +184,81 @@ class AttirePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionTab extends StatelessWidget {
|
||||
const _SectionTab({
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space4,
|
||||
vertical: UiConstants.space2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? UiColors.primary : UiColors.white,
|
||||
borderRadius: UiConstants.radiusFull,
|
||||
border: Border.all(
|
||||
color: isSelected ? UiColors.primary : UiColors.border,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: isSelected
|
||||
? UiTypography.footnote2m.white
|
||||
: UiTypography.footnote2m.textSecondary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
const _SectionHeader({required this.title, required this.count});
|
||||
|
||||
final String title;
|
||||
final int count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Text(title, style: UiTypography.headline4b),
|
||||
const SizedBox(width: UiConstants.space2),
|
||||
Text('($count)', style: UiTypography.body1m.textSecondary),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptySection extends StatelessWidget {
|
||||
const _EmptySection({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: UiConstants.space6),
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const Icon(UiIcons.shirt, size: 48, color: UiColors.iconInactive),
|
||||
const SizedBox(height: UiConstants.space4),
|
||||
Text(message, style: UiTypography.body1m.textSecondary),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user