| ํญ๋ชฉ | ๋ด์ฉ |
| Invoke | /console:feature |
| Aliases | @console-feature |
| Tools | Read, Edit, Write, Glob, Grep |
| Model | sonnet |
Console Feature Agent#
A specialized agent for developing Console (admin) feature modules.
Triggers#
/console:feature or @console-feature
- Console, admin, Admin, Employee feature development
- KPI card, table, filtering implementation
Console Feature Structure#
feature/console/{feature_name}/lib/src/
โโโ route/{feature}_route.dart # ๋ผ์ฐํธ ์ ์
โโโ domain/
โ โโโ entities/ # ๋๋ฉ์ธ ์ํฐํฐ
โ โโโ repositories/ # I{Feature}Repository
โ โโโ usecases/ # ์ ์ค์ผ์ด์ค
โ โโโ failures/ # ์คํจ ์ฒ๋ฆฌ
โโโ data/
โ โโโ repository/ # ๋ฆฌํฌ์งํ ๋ฆฌ ๊ตฌํ
โโโ presentation/
โโโ blocs/{feature}/ # BLoC (Event, State)
โโโ pages/
โ โโโ {feature}_page.dart # ๋ฉ์ธ ํ์ด์ง
โ โโโ components/ # ํ์ด์ง ์ปดํฌ๋ํธ
โโโ utils/ # ๊ฒ์ ํ๋ผ๋ฏธํฐ ๋ฑ
Core Patterns#
1. KPI Card Component#
/// KPI card - display key metrics
final class SalesKpiCard extends StatelessWidget {
const SalesKpiCard({
required this.salesDataItems,
this.isLoading = false,
super.key,
});
final List<pod.BookSalesData> salesDataItems;
final bool isLoading;
@override
Widget build(BuildContext context) {
// Data aggregation
final totalRevenue = salesDataItems.fold<double>(
0, (sum, item) => sum + item.totalRevenue,
);
final totalSales = salesDataItems.fold<int>(
0, (sum, item) => sum + item.totalSales,
);
return Row(
children: [
_KpiCardItem(
icon: Assets.svg.iconCoin,
label: '์ด ๊ฒฐ์ ์ก',
value: 'โฉ${_formatCurrency(totalRevenue)}',
color: context.appColors.primaryNormal,
),
const Gap(Insets.medium),
_KpiCardItem(
icon: Assets.svg.actionCart,
label: '์ด ํ๋งค๋',
value: '${totalSales}๊ถ',
color: context.appColors.accentBlue,
),
],
);
}
}
2. Table Default Sort Setting#
// ๋ฐ์ดํฐ ๋ก๋ ์ default ์ ๋ ฌ ์ ์ฉ
context.read<FeatureBloc>().add(
FeatureEvent.loadData(
// Default sort: date descending
sortInfo: pod.SortInfo(
criteria: [
pod.SortCriteria(
field: pod.BookSortField.publishedAt,
order: pod.SortOrder.desc,
),
],
sorted: true,
unsorted: false,
),
),
);
3. Permission-based Data Loading#
// Check permissions from AuthBloc
final authState = context.read<AuthBloc>().state;
final scopes = authState is Authenticated
? (authState.user.userInfo?.scopeNames ?? [])
: <String>[];
final hasAdminScope = scopes.any((scope) => scope.contains('admin'));
// Admin: query all data
if (hasAdminScope) {
bloc.add(FeatureEvent.loadData(publisherId: 0)); // 0 = ์ ์ฒด
}
// Employee: query own publisher data only
else {
bloc.add(FeatureEvent.loadPublisher(0)); // ๋ด ์ถํ์ฌ ๋ก๋ ํ ๋ฐ์ดํฐ ์กฐํ
}
4. Tab-based Page Structure#
class _LoadedContent extends HookWidget {
@override
Widget build(BuildContext context) {
final selectedTabIndex = useState<int>(0);
return Column(
children: [
// KPI ์นด๋ ์์ญ
SalesKpiCard(salesDataItems: state.salesDataItems),
// ํญ ์์ญ
Row(
children: [
_TabButton(
title: 'ํ๋งค ๋์ ํต๊ณ',
isSelected: selectedTabIndex.value == 0,
onTap: () => selectedTabIndex.value = 0,
),
_TabButton(
title: 'ํ๋งค ์์ฝ',
isSelected: selectedTabIndex.value == 1,
onTap: () => selectedTabIndex.value = 1,
),
],
),
// Tab content
Expanded(
child: selectedTabIndex.value == 0
? StatisticsComponent(state: state)
: SummaryComponent(state: state),
),
],
);
}
}
5. Search Parameter Utility#
/// URL query parameter parsing utility
@immutable
class FeatureSearchParams extends Equatable {
const FeatureSearchParams({
this.searchQuery,
this.categoryId,
this.isActive,
this.startDate,
this.endDate,
});
factory FeatureSearchParams.parse(Map<String, String> queryParams) {
return FeatureSearchParams(
searchQuery: queryParams['q'],
categoryId: int.tryParse(queryParams['category'] ?? ''),
isActive: queryParams['active'] == 'true' ? true
: queryParams['active'] == 'false' ? false : null,
startDate: DateTime.tryParse(queryParams['startDate'] ?? ''),
endDate: DateTime.tryParse(queryParams['endDate'] ?? ''),
);
}
// String for filter comparison
String toComparisonString() =>
'$searchQuery|$categoryId|$isActive|$startDate|$endDate';
}
SortInfo Field Options#
| BookSortField | Description |
title | Book title |
authors | Author name |
publishedAt | Publication date |
createdAt | Creation date |
updatedAt | Last modified date |
pageCount | Page count |
rentalPriceLifetime | Lifetime rental price |
isbn | ISBN |
Checklist#
/feature:create - Feature module creation
/bloc - BLoC pattern implementation
/zenhub:manage - ZenHub issue management