feat: implement attire section toggles for required and non-essential items in AttirePage

This commit is contained in:
Achintha Isuru
2026-03-07 02:47:55 -05:00
parent 720bf247b3
commit c9a46a1a71

View File

@@ -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),
],
),
),
);
}
}