forked from hesabix/arc
11 KiB
Executable file
11 KiB
Executable file
سناریو افزودن قابلیت جستجو به جدول حسابها
وضعیت فعلی
بکند (Backend)
- ✅ Endpoint جستجو موجود است:
/api/v1/accounts/business/{business_id}(POST) - ✅ پارامترهای جستجو:
search: عبارت جستجو (جستجو درcodeوname)take: تعداد رکوردهاskip: تعداد رکوردهای رد شدهsort_by: فیلد مرتبسازی (codeیاname)sort_desc: جهت مرتبسازی
- ✅ نوع پاسخ: لیست مسطح (بدون ساختار درختی)
فرانت (Frontend)
- ❌ صفحه فعلی:
accounts_page.dartفقط از درخت حسابها استفاده میکند - ❌ جستجو: قابلیت جستجو وجود ندارد
- ✅ سرویس:
AccountService.searchAccounts()موجود است اما استفاده نمیشود
سناریو پیشنهادی
گزینه 1: افزودن جستجو به صفحه درختی فعلی (پیشنهادی)
مزایا:
- حفظ ساختار درختی برای نمایش
- جستجو سریع و ساده
- تغییرات کم در UI
معایب:
- نتایج جستجو در ساختار درختی نمایش داده میشود (ممکن است گیجکننده باشد)
پیادهسازی:
- افزودن یک
TextFieldبرای جستجو در بالای جدول - هنگام تایپ، فراخوانی endpoint جستجو
- نمایش نتایج جستجو در همان ساختار درختی (یا لیست مسطح)
- با پاک کردن جستجو، بازگشت به نمایش درختی کامل
گزینه 2: تبدیل به DataTableWidget (پیشنهادی برای آینده)
مزایا:
- استفاده از کامپوننت استاندارد با قابلیتهای کامل
- جستجو، فیلتر، مرتبسازی و صفحهبندی یکپارچه
- سازگاری با سایر صفحات
معایب:
- از دست رفتن نمایش درختی
- نیاز به تغییرات بیشتر در UI
پیادهسازی:
- تبدیل
accounts_page.dartبه استفاده ازDataTableWidget - تنظیم
endpointبه/api/v1/accounts/business/{business_id} - تنظیم
searchFieldsبه['code', 'name'] - تعریف ستونها:
code,name,account_type - نیاز به تغییر endpoint بکند برای پشتیبانی از
QueryInfo(یا استفاده از endpoint موجود)
گزینه 3: حالت ترکیبی (پیشنهادی برای تجربه کاربری بهتر)
مزایا:
- حفظ نمایش درختی
- جستجو سریع و کارآمد
- امکان نمایش نتایج جستجو به صورت لیست مسطح
معایب:
- پیچیدگی بیشتر در UI
پیادهسازی:
- افزودن یک
SearchBarدر بالای صفحه - دو حالت نمایش:
- حالت عادی: نمایش درختی کامل (endpoint
/tree) - حالت جستجو: نمایش لیست مسطح نتایج (endpoint
/business/{business_id})
- حالت عادی: نمایش درختی کامل (endpoint
- نمایش نتایج جستجو در یک
ListViewیاDataTableساده - دکمه "پاک کردن جستجو" برای بازگشت به حالت درختی
توصیه: گزینه 3 (حالت ترکیبی)
جزئیات پیادهسازی گزینه 3
1. تغییرات در فرانت (Frontend)
فایل: accounts_page.dart
// افزودن state برای جستجو
String? _searchQuery;
bool _isSearchMode = false;
List<AccountNode> _searchResults = [];
// افزودن SearchBar در AppBar یا بالای body
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'جستجو در کد و نام حساب...',
prefixIcon: Icon(Icons.search),
suffixIcon: _searchQuery != null && _searchQuery!.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () {
setState(() {
_searchQuery = null;
_isSearchMode = false;
_searchResults = [];
});
_fetch(); // بازگشت به درخت کامل
},
)
: null,
),
onChanged: (value) {
_debounceSearch(value);
},
)
// تابع جستجو با debounce
Timer? _searchDebounce;
void _debounceSearch(String query) {
_searchDebounce?.cancel();
_searchDebounce = Timer(Duration(milliseconds: 500), () {
if (query.trim().isEmpty) {
setState(() {
_searchQuery = null;
_isSearchMode = false;
_searchResults = [];
});
_fetch();
} else {
_performSearch(query.trim());
}
});
}
// تابع جستجو
Future<void> _performSearch(String query) async {
setState(() {
_loading = true;
_searchQuery = query;
_isSearchMode = true;
});
try {
final service = AccountService();
final result = await service.searchAccounts(
businessId: widget.businessId,
searchQuery: query,
limit: 100,
);
final items = (result['items'] as List?) ?? [];
final parsed = items
.map((n) => AccountNode.fromJson(Map<String, dynamic>.from(n as Map)))
.toList();
setState(() {
_searchResults = parsed;
_loading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_loading = false;
});
}
}
// تغییر build method برای نمایش نتایج جستجو
@override
Widget build(BuildContext context) {
final t = AppLocalizations.of(context);
if (_loading) return const Center(child: CircularProgressIndicator());
if (_error != null) return Center(child: Text(_error!));
// اگر در حالت جستجو هستیم، نتایج جستجو را نمایش بده
final itemsToShow = _isSearchMode ? _searchResults : _buildVisibleNodes();
return Scaffold(
appBar: AppBar(
title: Text(t.chartOfAccounts),
bottom: PreferredSize(
preferredSize: Size.fromHeight(60),
child: Padding(
padding: EdgeInsets.all(8.0),
child: TextField(
// ... کد SearchBar
),
),
),
),
body: _isSearchMode
? _buildSearchResultsList(itemsToShow)
: _buildTreeView(itemsToShow),
);
}
2. تغییرات در بکند (Backend) - اختیاری
اگر بخواهیم از QueryInfo استفاده کنیم:
# تغییر endpoint برای پشتیبانی از QueryInfo
@router.post("/business/{business_id}",
summary="جستجو و فیلتر حسابها",
description="جستجو در حسابها با قابلیت فیلتر، مرتبسازی و صفحهبندی",
)
@require_business_access("business_id")
def search_accounts(
request: Request,
business_id: int,
query_info: QueryInfo, # استفاده از QueryInfo به جای SearchAccountsRequest
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""جستجوی حسابها با QueryInfo"""
query = db.query(Account).filter(
(Account.business_id == None) | (Account.business_id == business_id)
)
# اعمال جستجو
if query_info.search:
search_term = f"%{query_info.search}%"
if query_info.search_fields:
# جستجو در فیلدهای مشخص شده
conditions = []
for field in query_info.search_fields:
if field == 'code':
conditions.append(Account.code.ilike(search_term))
elif field == 'name':
conditions.append(Account.name.ilike(search_term))
if conditions:
from sqlalchemy import or_
query = query.filter(or_(*conditions))
else:
# جستجو در code و name به صورت پیشفرض
query = query.filter(
(Account.code.ilike(search_term)) | (Account.name.ilike(search_term))
)
# ... ادامه کد
یا میتوانیم endpoint فعلی را نگه داریم و فقط از SearchAccountsRequest استفاده کنیم.
3. بهبود سرویس فرانت
فایل: account_service.dart
/// جستجوی حسابها
Future<Map<String, dynamic>> searchAccounts({
required int businessId,
String? searchQuery,
int limit = 50,
int skip = 0,
String? sortBy,
bool sortDesc = false,
}) async {
try {
final requestData = <String, dynamic>{
'take': limit,
'skip': skip,
'sort_by': sortBy ?? 'code',
'sort_desc': sortDesc,
};
if (searchQuery != null && searchQuery.isNotEmpty) {
requestData['search'] = searchQuery;
}
final res = await _client.post<Map<String, dynamic>>(
'/api/v1/accounts/business/$businessId',
data: requestData,
);
final responseData = res.data?['data'] as Map<String, dynamic>?;
return responseData ?? <String, dynamic>{
'items': <dynamic>[],
'total': 0,
'skip': skip,
'take': limit,
};
} catch (e) {
rethrow;
}
}
مراحل پیادهسازی
مرحله 1: بهبود سرویس (Frontend)
- تکمیل متد
searchAccountsدرAccountService - اضافه کردن پارامترهای
skip,sortBy,sortDesc
مرحله 2: افزودن UI جستجو (Frontend)
- افزودن
TextFieldبرای جستجو درAppBarیا بالایbody - پیادهسازی debounce برای جستجو
- افزودن state برای حالت جستجو
مرحله 3: نمایش نتایج جستجو (Frontend)
- ایجاد متد
_buildSearchResultsListبرای نمایش لیست مسطح - تغییر
buildmethod برای تشخیص حالت جستجو - افزودن دکمه پاک کردن جستجو
مرحله 4: تست و بهبود (اختیاری)
- تست جستجو با عبارات مختلف
- بهبود UX (loading state، empty state)
- اضافه کردن highlight نتایج جستجو
مرحله 5: بهبود بکند (اختیاری)
- تبدیل endpoint به استفاده از
QueryInfoبرای سازگاری بیشتر - اضافه کردن فیلتر بر اساس
account_type - بهبود عملکرد جستجو با index
نکات مهم
- Debounce: برای جلوگیری از درخواستهای زیاد، از debounce استفاده شود (500ms پیشنهادی)
- Loading State: هنگام جستجو، نمایش loading indicator
- Empty State: اگر نتیجهای یافت نشد، پیام مناسب نمایش داده شود
- Backward Compatibility: تغییرات نباید عملکرد فعلی را خراب کند
- Performance: برای دیتابیسهای بزرگ، استفاده از index روی
codeوnameتوصیه میشود
سوالات برای بررسی
- آیا میخواهید نتایج جستجو در ساختار درختی نمایش داده شود یا لیست مسطح؟
- آیا نیاز به فیلتر بر اساس
account_typeدارید؟ - آیا میخواهید endpoint بکند را به
QueryInfoتبدیل کنیم یا همانSearchAccountsRequestرا نگه داریم؟ - آیا نیاز به highlight کردن نتایج جستجو دارید؟