Proje: Okul.com.tr CRM · Hub: Okul.com.tr CRM — Conventions

CRM Liste Sayfası Pattern

Bileşenler

  1. useListFilters hook — URL senkronizasyonu, filtreler, sayfalama, sıralama
  2. GenericListPage — Tablo, filtreler, pagination, arama render’ı
  3. GenericDeleteDialog — Silme onay dialog’u

Temel Şablon

const DEFAULT_SORT: SortState = { column: "id", direction: "desc" };
const FILTER_KEY_MAPPINGS: Record<string, string> = { 
    uiFiltreAdi: 'api-filtre-adi' 
};
 
export default function EntityPage() {
    const [items, setItems] = useState<Entity[]>([]);
    const [loading, setLoading] = useState(true);
    const [totalItems, setTotalItems] = useState(0);
    const [lastPage, setLastPage] = useState(1);
    
    const initialFilters: GenericFilter[] = useMemo(() => [...], [referenceData]);
    
    const { page, pageCount, sort, handlers, buildApiParams, ... } = useListFilters({
        initialFilters,
        defaultSort: DEFAULT_SORT,
        filterKeyMappings: FILTER_KEY_MAPPINGS,
    });
 
    const fetchData = useCallback(async () => {
        setLoading(true);
        const params = buildApiParams({ 'includes[]': ['relation'] });
        const response = await EntityService.getWithParams(params);
        // set items, totalItems, lastPage
    }, [buildApiParams]);  // SADECE buildApiParams dep
 
    useEffect(() => { fetchData(); }, [fetchData]);
    
    return (
        <GenericListPage
            data={{ items, loading, error, totalItems }}
            columns={columns}
            search={page}
            currentPage={page}
            pageCount={pageCount}
            sort={sort}
            appliedFilters={appliedFilters}
            onSearch={handlers.onSearch}
            onPageChange={handlers.onPageChange}
            // ...tüm handlers spread edilir
        />
    );
}

useListFilters Çıktıları

AlanAçıklama
search, page, pageCount, sortURL’den gelen değerler
appliedFiltersURL’deki aktif filtreler (key-value map)
filters / setFiltersFilterSheet için lokal state
handlersTüm event handler’lar (onSearch, onPageChange, onSort…)
buildApiParams()API çağrısı için param objesi oluşturur
displayFiltersUI’da gösterilen aktif filtre badge’leri
isFilterSidebarOpenFilterSheet açık/kapalı

buildApiParams Ne Döner

{
    page: urlPage,
    per_page: urlPageCount,
    sort: direction === 'desc' ? `-${column}` : column,
    search: urlSearch,          // varsa
    'filter[apiKey]': value,    // her aktif filtre için
    ...baseParams               // ekstra geçirilen params
}

filterKeyMappings Kullanımı

UI’daki filtre adını API’nin beklediği query param adına çevirir:

const FILTER_KEY_MAPPINGS = {
    'city': 'location-1',     // UI'da 'city' → API'de 'filter[location-1]'
    'district': 'location-2',
    'school_name': 'school-name'
};

Hiyerarşik Filtreler

useListFilters hierarchicalFilters seçeneği destekler:

hierarchicalFilters: [{
    parent: 'city',
    child: 'district',
    loadChildren: async (cityId) => await LocationService.getDistricts(cityId),
    placeholder: 'Önce şehir seçin'
}]

Parent seçildiğinde child options otomatik yüklenir, parent temizlendiğinde child sıfırlanır.

Referans Veri Yükleme

Filtre seçenekleri için referans veriler loadReferenceData callback’inde Promise.all ile paralel yüklenir. initialFilters bu verilere useMemo ile bağlıdır.