269 lines
11 KiB
TypeScript
269 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import EntriesList from "@/features/entries/components/entries-list";
|
|
import EntriesPanelHeader from "@/features/entries/components/entries-panel-header";
|
|
import EntriesPanelModals from "@/features/entries/components/entries-panel-modals";
|
|
import SchedulesList from "@/features/entries/components/schedules-list";
|
|
import useEntriesPanelCrud from "@/features/entries/components/use-entries-panel-crud";
|
|
import useEntriesPanelFilters from "@/features/entries/components/use-entries-panel-filters";
|
|
import useEntries from "@/features/entries/hooks/use-entries";
|
|
import useSchedules from "@/features/entries/hooks/use-schedules";
|
|
import useGroupSettings from "@/features/groups/hooks/use-group-settings";
|
|
import useTags from "@/features/tags/hooks/use-tags";
|
|
import { useEntryMutation } from "@/hooks/entry-mutation-context";
|
|
import { useGroupsContext } from "@/hooks/groups-context";
|
|
import { useNotificationsContext } from "@/hooks/notifications-context";
|
|
import useUserSettings from "@/hooks/use-user-settings";
|
|
|
|
type ListProgressSignalProps = {
|
|
hasMore: boolean;
|
|
shownCount: number;
|
|
totalCount: number;
|
|
noun: "entries" | "schedules";
|
|
};
|
|
|
|
function ListProgressSignal({ hasMore, shownCount, totalCount, noun }: ListProgressSignalProps) {
|
|
if (totalCount <= 0) return null;
|
|
|
|
return (
|
|
<div
|
|
className={`mt-3 flex items-center justify-center gap-2 rounded-lg border px-3 py-2 text-xs ${hasMore ? "border-accent-weak bg-accent-soft text-[color:var(--color-text)]" : "border-accent-weak bg-panel text-soft"}`}
|
|
aria-live="polite"
|
|
>
|
|
<span className={`inline-flex h-5 w-5 items-center justify-center rounded-full border ${hasMore ? "border-accent bg-panel text-[color:var(--color-accent)]" : "border-accent-weak text-soft"}`}>
|
|
{hasMore ? "\u21e3" : "\u2713"}
|
|
</span>
|
|
<span>
|
|
{hasMore
|
|
? `Keep scrolling for more ${noun} (${shownCount} of ${totalCount})`
|
|
: `You have reached the end of ${noun} (${totalCount} total)`}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function EntriesPanel() {
|
|
const { groups, activeGroupId } = useGroupsContext();
|
|
const { entries, loading: entriesLoading, error: entriesError, createEntry, updateEntry, deleteEntry } = useEntries(activeGroupId);
|
|
const { schedules, loading: schedulesLoading, error: schedulesError, createSchedule, updateSchedule, deleteSchedule } = useSchedules(activeGroupId);
|
|
const { settings: userSettings } = useUserSettings();
|
|
const { notify } = useNotificationsContext();
|
|
const { notifyEntryMutation } = useEntryMutation();
|
|
const { tags: tagSuggestions } = useTags(activeGroupId);
|
|
const { settings: groupSettings } = useGroupSettings(activeGroupId);
|
|
|
|
const activeGroup = groups.find(group => group.id === activeGroupId) || null;
|
|
const canManageTags = Boolean(activeGroup && (activeGroup.role !== "MEMBER" || groupSettings.allowMemberTagManage));
|
|
const emptyTagActionLabel = canManageTags
|
|
? "No Tags Assigned Yet - Click To Assign Tags"
|
|
: "No Tags Assigned Yet - Contact Your Group Admin";
|
|
|
|
const pageSize = Math.max(1, Number(userSettings.entryPanelPageSize || 10));
|
|
|
|
const {
|
|
entryTab,
|
|
setEntryTab,
|
|
filters,
|
|
setFilters,
|
|
filterOpen,
|
|
setFilterOpen,
|
|
activeFilterCount,
|
|
filteredEntries,
|
|
filteredSchedules,
|
|
visibleEntries,
|
|
visibleSchedules,
|
|
hasMoreEntries,
|
|
hasMoreSchedules,
|
|
entriesLoadSentinelRef,
|
|
schedulesLoadSentinelRef,
|
|
clearFilters,
|
|
onFilterAddTag,
|
|
onFilterToggleTag
|
|
} = useEntriesPanelFilters({ entries, schedules, pageSize });
|
|
|
|
const {
|
|
newEntryOpen,
|
|
setNewEntryOpen,
|
|
newScheduleOpen,
|
|
setNewScheduleOpen,
|
|
entryDetailsOpen,
|
|
setEntryDetailsOpen,
|
|
scheduleDetailsOpen,
|
|
setScheduleDetailsOpen,
|
|
confirmDeleteOpen,
|
|
setConfirmDeleteOpen,
|
|
deleteTarget,
|
|
setDeleteTarget,
|
|
selectedEntryIndex,
|
|
entryRemovedTags,
|
|
setEntryRemovedTags,
|
|
scheduleRemovedTags,
|
|
setScheduleRemovedTags,
|
|
entryForm,
|
|
setEntryForm,
|
|
entryDetailsForm,
|
|
setEntryDetailsForm,
|
|
entryDetailsOriginal,
|
|
scheduleForm,
|
|
setScheduleForm,
|
|
scheduleDetailsForm,
|
|
setScheduleDetailsForm,
|
|
scheduleDetailsOriginal,
|
|
amountInputRef,
|
|
tagsInputRef,
|
|
handleEmptyTagAction,
|
|
hasEntryChanges,
|
|
hasScheduleChanges,
|
|
submitNewEntry,
|
|
submitNewSchedule,
|
|
openEntryDetails,
|
|
openScheduleDetails,
|
|
submitEntryUpdate,
|
|
submitScheduleUpdate,
|
|
confirmDelete,
|
|
prevEntry,
|
|
nextEntry
|
|
} = useEntriesPanelCrud({
|
|
filteredEntries,
|
|
schedules,
|
|
createEntry,
|
|
updateEntry,
|
|
deleteEntry,
|
|
createSchedule,
|
|
updateSchedule,
|
|
deleteSchedule,
|
|
notify,
|
|
notifyEntryMutation,
|
|
canManageTags
|
|
});
|
|
|
|
const listCounts = useMemo(() => {
|
|
return {
|
|
entriesShown: visibleEntries.length,
|
|
entriesTotal: filteredEntries.length,
|
|
schedulesShown: visibleSchedules.length,
|
|
schedulesTotal: filteredSchedules.length
|
|
};
|
|
}, [filteredEntries.length, filteredSchedules.length, visibleEntries.length, visibleSchedules.length]);
|
|
|
|
return (
|
|
<>
|
|
<div className="space-y-4">
|
|
<div className="panel panel-accent p-4">
|
|
<EntriesPanelHeader
|
|
entryTab={entryTab}
|
|
activeGroupId={activeGroupId}
|
|
activeFilterCount={activeFilterCount}
|
|
onTabChange={setEntryTab}
|
|
onOpenFilters={() => setFilterOpen(true)}
|
|
onOpenCreate={() => {
|
|
if (entryTab === "ENTRIES") {
|
|
setNewEntryOpen(true);
|
|
return;
|
|
}
|
|
setNewScheduleOpen(true);
|
|
}}
|
|
/>
|
|
|
|
{entryTab === "ENTRIES" ? (
|
|
<>
|
|
<EntriesList
|
|
activeGroupId={activeGroupId}
|
|
loading={entriesLoading}
|
|
entries={entries}
|
|
visibleEntries={visibleEntries}
|
|
activeFilterCount={activeFilterCount}
|
|
onOpenDetails={entry => openEntryDetails(entry.id)}
|
|
onClearFilters={clearFilters}
|
|
/>
|
|
<div ref={entriesLoadSentinelRef} className="h-1" aria-hidden="true" />
|
|
<ListProgressSignal
|
|
hasMore={hasMoreEntries}
|
|
shownCount={listCounts.entriesShown}
|
|
totalCount={listCounts.entriesTotal}
|
|
noun="entries"
|
|
/>
|
|
</>
|
|
) : (
|
|
<>
|
|
<SchedulesList
|
|
activeGroupId={activeGroupId}
|
|
loading={schedulesLoading}
|
|
schedules={schedules}
|
|
visibleSchedules={visibleSchedules}
|
|
activeFilterCount={activeFilterCount}
|
|
onOpenDetails={schedule => openScheduleDetails(schedule.id)}
|
|
onClearFilters={clearFilters}
|
|
/>
|
|
<div ref={schedulesLoadSentinelRef} className="h-1" aria-hidden="true" />
|
|
<ListProgressSignal
|
|
hasMore={hasMoreSchedules}
|
|
shownCount={listCounts.schedulesShown}
|
|
totalCount={listCounts.schedulesTotal}
|
|
noun="schedules"
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<EntriesPanelModals
|
|
activeGroupId={activeGroupId}
|
|
entriesError={entriesError}
|
|
schedulesError={schedulesError}
|
|
tagSuggestions={tagSuggestions}
|
|
canManageTags={canManageTags}
|
|
emptyTagActionLabel={emptyTagActionLabel}
|
|
handleEmptyTagAction={handleEmptyTagAction}
|
|
filterOpen={filterOpen}
|
|
setFilterOpen={setFilterOpen}
|
|
filters={filters}
|
|
setFilters={setFilters}
|
|
activeFilterCount={activeFilterCount}
|
|
clearFilters={clearFilters}
|
|
onFilterAddTag={onFilterAddTag}
|
|
onFilterToggleTag={onFilterToggleTag}
|
|
newEntryOpen={newEntryOpen}
|
|
setNewEntryOpen={setNewEntryOpen}
|
|
entryForm={entryForm}
|
|
setEntryForm={setEntryForm}
|
|
submitNewEntry={submitNewEntry}
|
|
amountInputRef={amountInputRef}
|
|
tagsInputRef={tagsInputRef}
|
|
newScheduleOpen={newScheduleOpen}
|
|
setNewScheduleOpen={setNewScheduleOpen}
|
|
scheduleForm={scheduleForm}
|
|
setScheduleForm={setScheduleForm}
|
|
submitNewSchedule={submitNewSchedule}
|
|
entryDetailsOpen={entryDetailsOpen}
|
|
setEntryDetailsOpen={setEntryDetailsOpen}
|
|
entryDetailsForm={entryDetailsForm}
|
|
setEntryDetailsForm={setEntryDetailsForm}
|
|
entryDetailsOriginal={entryDetailsOriginal}
|
|
hasEntryChanges={hasEntryChanges}
|
|
submitEntryUpdate={submitEntryUpdate}
|
|
entryRemovedTags={entryRemovedTags}
|
|
setEntryRemovedTags={setEntryRemovedTags}
|
|
prevEntry={prevEntry}
|
|
nextEntry={nextEntry}
|
|
selectedEntryIndex={selectedEntryIndex}
|
|
filteredEntriesCount={filteredEntries.length}
|
|
scheduleDetailsOpen={scheduleDetailsOpen}
|
|
setScheduleDetailsOpen={setScheduleDetailsOpen}
|
|
scheduleDetailsForm={scheduleDetailsForm}
|
|
setScheduleDetailsForm={setScheduleDetailsForm}
|
|
scheduleDetailsOriginal={scheduleDetailsOriginal}
|
|
hasScheduleChanges={hasScheduleChanges}
|
|
submitScheduleUpdate={submitScheduleUpdate}
|
|
scheduleRemovedTags={scheduleRemovedTags}
|
|
setScheduleRemovedTags={setScheduleRemovedTags}
|
|
confirmDeleteOpen={confirmDeleteOpen}
|
|
setConfirmDeleteOpen={setConfirmDeleteOpen}
|
|
deleteTarget={deleteTarget}
|
|
setDeleteTarget={setDeleteTarget}
|
|
confirmDelete={confirmDelete}
|
|
/>
|
|
</>
|
|
);
|
|
} |