|
12 | 12 | } |
13 | 13 |
|
14 | 14 | @if (committees() && !committeesLoading()) { |
15 | | - <div class="grid grid-cols-1 lg:grid-cols-4 gap-6 items-stretch"> |
16 | | - <div class="lg:col-span-3 items-stretch h-full"> |
17 | | - <lfx-card header="Committees" styleClass="h-full" class="h-full"> |
18 | | - <!-- Search and Filter Controls --> |
19 | | - <div class="flex flex-col sm:flex-row gap-4 mb-6"> |
20 | | - <!-- Search Input --> |
21 | | - <div class="flex-1"> |
22 | | - <lfx-input-text |
23 | | - [form]="searchForm" |
24 | | - control="search" |
25 | | - placeholder="Search committees..." |
26 | | - icon="fa-light fa-search" |
27 | | - styleClass="w-full" |
28 | | - size="small"></lfx-input-text> |
29 | | - </div> |
| 15 | + <div class="flex flex-col gap-6"> |
| 16 | + <div class="flex items-center justify-between p-4 border-b border-gray-100"> |
| 17 | + <div> |
| 18 | + <h2 class="text-[16px] font-bold text-gray-900">Committee Management</h2> |
| 19 | + <p class="text-[12px] font-normal text-gray-500 mt-1"> |
| 20 | + Manage committees, their settings, and organizational structure. Sub-committees are indicated with indentation. |
| 21 | + </p> |
| 22 | + </div> |
| 23 | + <lfx-button |
| 24 | + label="New Committee" |
| 25 | + icon="fa-light fa-plus" |
| 26 | + severity="primary" |
| 27 | + (onClick)="openCreateDialog()" |
| 28 | + size="small" |
| 29 | + data-testid="committee-new-cta"> |
| 30 | + </lfx-button> |
| 31 | + </div> |
30 | 32 |
|
31 | | - <!-- Category Filter Dropdown --> |
32 | | - <div class="w-full sm:w-64"> |
33 | | - <lfx-select |
34 | | - [form]="searchForm" |
35 | | - control="category" |
36 | | - size="small" |
37 | | - [options]="categories()" |
38 | | - placeholder="Select Category" |
39 | | - [showClear]="true" |
40 | | - styleClass="w-full" |
41 | | - (onChange)="onCategoryChange($event.value)"></lfx-select> |
| 33 | + <!-- Statistics Cards --> |
| 34 | + <div class="grid grid-cols-1 md:grid-cols-4 gap-3 w-full"> |
| 35 | + <lfx-card styleClass="bg-white border border-gray-100 shadow-sm h-full" data-testid="stats-total-committees"> |
| 36 | + <div class="p-3"> |
| 37 | + <div class="text-[10px] text-gray-500 font-normal mb-1">Total Committees</div> |
| 38 | + <div class="flex items-center gap-2"> |
| 39 | + <span class="text-[24px] text-gray-900 font-normal">{{ totalCommittees() }}</span> |
| 40 | + <div class="flex items-center gap-1 text-emerald-600"> |
| 41 | + <i class="fa-light fa-trending-up text-[8px]"></i> |
| 42 | + <span class="text-[10px] font-medium">+2 this week</span> |
| 43 | + </div> |
42 | 44 | </div> |
43 | 45 | </div> |
| 46 | + </lfx-card> |
44 | 47 |
|
45 | | - @if (committees().length > 0) { |
46 | | - <!-- Committees Table --> |
47 | | - <lfx-table |
48 | | - [value]="filteredCommittees()" |
49 | | - [paginator]="true" |
50 | | - [totalRecords]="totalRecords()" |
51 | | - [rowsPerPageOptions]="[10, 25, 50]" |
52 | | - [showCurrentPageReport]="true" |
53 | | - currentPageReportTemplate="Showing {first} to {last} of {totalRecords} committees" |
54 | | - (onPage)="onPageChange($event)" |
55 | | - styleClass="p-datatable-sm animate-fade-in-up"> |
56 | | - <!-- Header Template --> |
57 | | - <ng-template #header> |
58 | | - <tr> |
59 | | - <th> |
60 | | - <span class="text-sm font-sans text-gray-500">Name</span> |
61 | | - </th> |
62 | | - <th> |
63 | | - <span class="text-sm font-sans text-gray-500">Type</span> |
64 | | - </th> |
65 | | - <th> |
66 | | - <div class="flex justify-center"> |
67 | | - <span class="text-sm font-sans text-gray-500">Members</span> |
68 | | - </div> |
69 | | - </th> |
70 | | - <th> |
71 | | - <div class="flex justify-center"> |
72 | | - <span class="text-sm font-sans text-gray-500">Voting</span> |
73 | | - </div> |
74 | | - </th> |
75 | | - <th> |
76 | | - <div class="flex justify-center"> |
77 | | - <span class="text-sm font-sans text-gray-500">Public</span> |
78 | | - </div> |
79 | | - </th> |
80 | | - <th> |
81 | | - <div class="flex justify-end"> |
82 | | - <span class="text-sm font-sans text-gray-500">Last Updated</span> |
83 | | - </div> |
84 | | - </th> |
85 | | - <th style="width: 80px"> |
86 | | - <div class="flex justify-center"> |
87 | | - <span class="text-sm font-sans text-gray-500">Actions</span> |
88 | | - </div> |
89 | | - </th> |
90 | | - </tr> |
91 | | - </ng-template> |
92 | | - |
93 | | - <!-- Body Template --> |
94 | | - <ng-template #body let-committee> |
95 | | - <tr> |
96 | | - <td> |
97 | | - <a [routerLink]="['/project', project()?.slug, 'committees', committee.id]" class="text-primary hover:text-primary-600 font-medium text-sm"> |
98 | | - {{ committee.name }} |
99 | | - </a> |
100 | | - </td> |
101 | | - <td> |
102 | | - <span class="text-sm font-sans text-gray-500">{{ committee.category || 'Uncategorized' }}</span> |
103 | | - </td> |
104 | | - <td class="text-center"> |
105 | | - <span class="text-sm font-sans text-gray-500"> {{ committee.total_members || 0 }} </span> |
106 | | - </td> |
107 | | - <td class="text-center"> |
108 | | - <span class="text-sm font-sans text-gray-500"> {{ committee.enable_voting ? 'Yes' : 'No' }} </span> |
109 | | - </td> |
110 | | - <td class="text-center"> |
111 | | - <i class="fa-light fa-circle-check text-green-500" *ngIf="committee.public_enabled"></i> |
112 | | - <i class="fa-light fa-circle-xmark text-red-500" *ngIf="!committee.public_enabled"></i> |
113 | | - </td> |
114 | | - <td class="text-right"> |
115 | | - <span class="text-sm font-sans text-gray-500"> {{ formatDate(committee.updated_at) }} </span> |
116 | | - </td> |
117 | | - <td class="text-center relative"> |
118 | | - <lfx-button |
119 | | - icon="fa-light fa-ellipsis-vertical" |
120 | | - [text]="true" |
121 | | - [rounded]="true" |
122 | | - size="small" |
123 | | - severity="secondary" |
124 | | - (click)="toggleActionMenu($event, committee, actionMenu)"></lfx-button> |
125 | | - </td> |
126 | | - </tr> |
127 | | - </ng-template> |
128 | | - |
129 | | - <!-- Empty Message Template --> |
130 | | - <ng-template #emptymessage> |
131 | | - <tr> |
132 | | - <td colspan="7" class="text-center py-8"> |
133 | | - <div class="flex flex-col items-center gap-2"> |
134 | | - <i class="fa-light fa-users text-4xl text-gray-400"></i> |
135 | | - <p class="text-gray-600">No committees found</p> |
136 | | - <p class="text-sm text-gray-500">Try adjusting your search or filters</p> |
137 | | - </div> |
138 | | - </td> |
139 | | - </tr> |
140 | | - </ng-template> |
141 | | - </lfx-table> |
142 | | - |
143 | | - <!-- Single action menu for all committees --> |
144 | | - <lfx-menu #actionMenu [model]="actionMenuItems" [popup]="true"></lfx-menu> |
| 48 | + <lfx-card styleClass="bg-white border border-gray-100 shadow-sm h-full" data-testid="stats-public-committees"> |
| 49 | + <div class="p-3"> |
| 50 | + <div class="text-[10px] text-gray-500 font-normal mb-1">Public Committees</div> |
| 51 | + <div class="flex items-center gap-2"> |
| 52 | + <span class="text-[24px] text-gray-900 font-normal">{{ publicCommittees() }}</span> |
| 53 | + <div class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-blue-100 text-blue-600 border border-blue-200"> |
| 54 | + <span class="text-[9px] font-medium" |
| 55 | + >{{ totalCommittees() > 0 ? ((publicCommittees() / totalCommittees()) * 100 | number: '1.0-0') : 0 }}% of total</span |
| 56 | + > |
| 57 | + </div> |
| 58 | + </div> |
| 59 | + </div> |
| 60 | + </lfx-card> |
145 | 61 |
|
146 | | - <!-- Confirmation dialog --> |
147 | | - <p-confirmDialog></p-confirmDialog> |
148 | | - } @else { |
149 | | - <div class="flex justify-center items-center h-full"> |
150 | | - <div class="text-center py-8"> |
151 | | - <i class="fa-light fa-people-group text-4xl text-gray-400 mb-4"></i> |
152 | | - <h3 class="text-lg font-medium text-gray-900 mb-2">No Committees Yet</h3> |
153 | | - <p class="text-gray-600 mb-4">This project doesn't have any committees yet.</p> |
154 | | - <lfx-button label="Add First Committee" icon="fa-light fa-people-group" severity="secondary" (onClick)="openCreateDialog()"></lfx-button> |
| 62 | + <lfx-card styleClass="bg-white border border-gray-100 shadow-sm h-full" data-testid="stats-active-voting"> |
| 63 | + <div class="p-3"> |
| 64 | + <div class="text-[10px] text-gray-500 font-normal mb-1">Active Voting</div> |
| 65 | + <div class="flex items-center gap-2"> |
| 66 | + <span class="text-[24px] text-gray-900 font-normal">{{ activeVoting() }}</span> |
| 67 | + <div class="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-green-100 text-green-600 border border-green-200"> |
| 68 | + <span class="text-[9px] font-medium">Voting enabled</span> |
155 | 69 | </div> |
156 | 70 | </div> |
157 | | - } |
| 71 | + </div> |
| 72 | + </lfx-card> |
| 73 | + |
| 74 | + <lfx-card styleClass="bg-white border border-gray-100 shadow-sm h-full items-center" data-testid="stats-upcoming-meeting"> |
| 75 | + <div class="p-3"> |
| 76 | + <div class="text-[10px] text-gray-500 font-normal mb-1">Upcoming Meeting</div> |
| 77 | + <lfx-upcoming-committee-meeting></lfx-upcoming-committee-meeting> |
| 78 | + </div> |
158 | 79 | </lfx-card> |
159 | 80 | </div> |
160 | | - <div class="lg:col-span-1 items-stretch flex flex-col"> |
161 | | - <div class="sticky top-24"> |
162 | | - <lfx-card header="Quick Actions"> |
163 | | - <lfx-menu [model]="menuItems" styleClass="border-none"></lfx-menu> |
164 | | - </lfx-card> |
165 | 81 |
|
166 | | - <div class="flex flex-col gap-4 mt-6"> |
167 | | - <lfx-card header="Upcoming Meeting" styleClass="h-full" class="h-full"> |
168 | | - <lfx-upcoming-committee-meeting></lfx-upcoming-committee-meeting> |
169 | | - </lfx-card> |
| 82 | + <!-- Committee Management Section --> |
| 83 | + <lfx-card styleClass="h-full" class="h-full"> |
| 84 | + <!-- Search and Filter Controls --> |
| 85 | + <div class="flex flex-col sm:flex-row gap-4 mb-6 p-4"> |
| 86 | + <!-- Search Input --> |
| 87 | + <div class="flex-1"> |
| 88 | + <lfx-input-text |
| 89 | + [form]="searchForm" |
| 90 | + control="search" |
| 91 | + placeholder="Search committees..." |
| 92 | + icon="fa-light fa-search" |
| 93 | + styleClass="w-full" |
| 94 | + size="small" |
| 95 | + data-testid="committee-search-input"></lfx-input-text> |
| 96 | + </div> |
| 97 | + |
| 98 | + <!-- Committee Type Filter Dropdown --> |
| 99 | + <div class="w-full sm:w-64"> |
| 100 | + <lfx-select |
| 101 | + [form]="searchForm" |
| 102 | + control="category" |
| 103 | + size="small" |
| 104 | + [options]="categories()" |
| 105 | + placeholder="Select Committee Type" |
| 106 | + [showClear]="true" |
| 107 | + styleClass="w-full" |
| 108 | + (onChange)="onCategoryChange($event.value)" |
| 109 | + data-testid="committee-type-filter"></lfx-select> |
| 110 | + </div> |
| 111 | + |
| 112 | + <!-- Voting Status Filter Dropdown --> |
| 113 | + <div class="w-full sm:w-64"> |
| 114 | + <lfx-select |
| 115 | + [form]="searchForm" |
| 116 | + control="votingStatus" |
| 117 | + size="small" |
| 118 | + [options]="votingStatusOptions()" |
| 119 | + placeholder="Select Voting Status" |
| 120 | + [showClear]="true" |
| 121 | + styleClass="w-full" |
| 122 | + (onChange)="onVotingStatusChange($event.value)" |
| 123 | + data-testid="committee-voting-status-filter"></lfx-select> |
170 | 124 | </div> |
171 | 125 | </div> |
172 | | - </div> |
| 126 | + |
| 127 | + @if (committees().length > 0) { |
| 128 | + <!-- Committee Table --> |
| 129 | + <div class="px-4 pb-4"> |
| 130 | + <lfx-committee-table |
| 131 | + [committees]="filteredCommittees()" |
| 132 | + (editCommittee)="onEditCommittee($event)" |
| 133 | + (deleteCommittee)="onDeleteCommittee($event)" |
| 134 | + (viewCommittee)="onViewCommittee($event)" |
| 135 | + data-testid="committee-dashboard-table"> |
| 136 | + </lfx-committee-table> |
| 137 | + </div> |
| 138 | + |
| 139 | + <!-- Confirmation dialog --> |
| 140 | + <p-confirmDialog></p-confirmDialog> |
| 141 | + } @else { |
| 142 | + <div class="flex justify-center items-center h-full p-4"> |
| 143 | + <div class="text-center py-8"> |
| 144 | + <i class="fa-light fa-people-group text-4xl text-gray-400 mb-4"></i> |
| 145 | + <h3 class="text-lg font-medium text-gray-900 mb-2">No Committees Yet</h3> |
| 146 | + <p class="text-gray-600 mb-4">This project doesn't have any committees yet.</p> |
| 147 | + <lfx-button |
| 148 | + label="Add First Committee" |
| 149 | + icon="fa-light fa-people-group" |
| 150 | + severity="secondary" |
| 151 | + (onClick)="openCreateDialog()" |
| 152 | + data-testid="committee-add-first"></lfx-button> |
| 153 | + </div> |
| 154 | + </div> |
| 155 | + } |
| 156 | + </lfx-card> |
173 | 157 | </div> |
174 | 158 | } |
175 | 159 | </div> |
0 commit comments