add assignee and category filter to ticket system

This commit is contained in:
Yinyin Liu 2026-05-05 08:57:02 +02:00
parent c5678b0856
commit 1bda67fec6
7 changed files with 103 additions and 2 deletions

View File

@ -2591,19 +2591,38 @@ public class Controller : ControllerBase
.ToList()
.ToDictionary(i => i.Id);
var assigneeIds = tickets
.Where(t => t.AssigneeId.HasValue)
.Select(t => t.AssigneeId!.Value)
.Distinct()
.ToList();
var assigneesById = assigneeIds.Count == 0
? new Dictionary<Int64, User>()
: Db.Users
.Where(u => assigneeIds.Contains(u.Id))
.ToList()
.ToDictionary(u => u.Id);
var summaries = tickets.Select(t =>
{
Installation? installation = null;
if (t.InstallationId.HasValue)
installationsById.TryGetValue(t.InstallationId.Value, out installation);
User? assignee = null;
if (t.AssigneeId.HasValue)
assigneesById.TryGetValue(t.AssigneeId.Value, out assignee);
return new
{
t.Id, t.Subject, t.Status, t.Priority, t.Category, t.SubCategory,
t.InstallationId, t.CreatedAt, t.UpdatedAt,
t.CustomSubCategory, t.CustomCategory,
t.AssigneeId,
installationName = installation?.Name ?? (t.InstallationId.HasValue ? $"#{t.InstallationId}" : "No installation"),
distributionPartner = installation?.DistributionPartner ?? ""
distributionPartner = installation?.DistributionPartner ?? "",
assigneeName = assignee?.Name
};
});

View File

@ -27,6 +27,7 @@ import {
TicketSummary,
TicketStatus,
TicketPriority,
AdminUser,
categoryKeys,
subCategoryKeys,
getCategoryDisplayLabel,
@ -53,9 +54,12 @@ function TicketList() {
const navigate = useNavigate();
const intl = useIntl();
const [tickets, setTickets] = useState<TicketSummary[]>([]);
const [adminUsers, setAdminUsers] = useState<AdminUser[]>([]);
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState<number[]>([]);
const [partnerFilter, setPartnerFilter] = useState<string>('');
const [assigneeFilter, setAssigneeFilter] = useState<string>('');
const [categoryFilter, setCategoryFilter] = useState<string>('');
const [createOpen, setCreateOpen] = useState(false);
const [error, setError] = useState('');
@ -68,12 +72,26 @@ function TicketList() {
useEffect(() => {
fetchTickets();
axiosConfig
.get('/GetAdminUsers')
.then((res) => setAdminUsers(res.data))
.catch(() => {});
}, []);
const partnerOptions = Array.from(
new Set(tickets.map((t) => t.distributionPartner).filter((p) => p && p.trim() !== ''))
).sort();
const assigneeOptions = adminUsers
.filter((u) => {
const name = (u.name ?? '').toLowerCase();
return (
!name.includes('inesco energy master admin') &&
!name.includes('paal myhre')
);
})
.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''));
const filtered = tickets
.filter((t) => {
const matchesSearch =
@ -82,7 +100,14 @@ function TicketList() {
t.installationName.toLowerCase().includes(search.toLowerCase());
const matchesStatus = statusFilter.length === 0 || statusFilter.includes(t.status);
const matchesPartner = partnerFilter === '' || t.distributionPartner === partnerFilter;
return matchesSearch && matchesStatus && matchesPartner;
const matchesAssignee =
assigneeFilter === '' ||
(assigneeFilter === '__unassigned__'
? !t.assigneeId
: t.assigneeId === Number(assigneeFilter));
const matchesCategory =
categoryFilter === '' || t.category === Number(categoryFilter);
return matchesSearch && matchesStatus && matchesPartner && matchesAssignee && matchesCategory;
})
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
@ -176,6 +201,53 @@ function TicketList() {
))}
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 200 }}>
<InputLabel>
<FormattedMessage id="assignee" defaultMessage="Assignee" />
</InputLabel>
<Select
value={assigneeFilter}
label={intl.formatMessage({
id: 'assignee',
defaultMessage: 'Assignee'
})}
onChange={(e) => setAssigneeFilter(e.target.value as string)}
>
<MenuItem value="">
<FormattedMessage id="allAssignees" defaultMessage="All Assignees" />
</MenuItem>
<MenuItem value="__unassigned__">
<FormattedMessage id="unassigned" defaultMessage="Unassigned" />
</MenuItem>
{assigneeOptions.map((u) => (
<MenuItem key={u.id} value={String(u.id)}>
{u.name}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl size="small" sx={{ minWidth: 200 }}>
<InputLabel>
<FormattedMessage id="category" defaultMessage="Category" />
</InputLabel>
<Select
value={categoryFilter}
label={intl.formatMessage({
id: 'category',
defaultMessage: 'Category'
})}
onChange={(e) => setCategoryFilter(e.target.value as string)}
>
<MenuItem value="">
<FormattedMessage id="allCategories" defaultMessage="All Categories" />
</MenuItem>
{Object.entries(categoryKeys).map(([val, msg]) => (
<MenuItem key={val} value={val}>
{intl.formatMessage(msg)}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
{error && (

View File

@ -267,6 +267,8 @@ export type TicketSummary = {
distributionPartner: string;
customSubCategory: string | null;
customCategory: string | null;
assigneeId: number | null;
assigneeName: string | null;
};
export type AdminUser = {

View File

@ -588,6 +588,8 @@
"category": "Kategorie",
"allStatuses": "Alle Status",
"allPartners": "Alle Partner",
"allAssignees": "Alle Zuständigen",
"allCategories": "Alle Kategorien",
"createdAt": "Erstellt",
"noTickets": "Keine Tickets gefunden.",
"backToTickets": "Zurück zu Tickets",

View File

@ -336,6 +336,8 @@
"category": "Category",
"allStatuses": "All Statuses",
"allPartners": "All Partners",
"allAssignees": "All Assignees",
"allCategories": "All Categories",
"createdAt": "Created",
"noTickets": "No tickets found.",
"backToTickets": "Back to Tickets",

View File

@ -588,6 +588,8 @@
"category": "Catégorie",
"allStatuses": "Tous les statuts",
"allPartners": "Tous les partenaires",
"allAssignees": "Tous les responsables",
"allCategories": "Toutes les catégories",
"createdAt": "Créé",
"noTickets": "Aucun ticket trouvé.",
"backToTickets": "Retour aux tickets",

View File

@ -588,6 +588,8 @@
"category": "Categoria",
"allStatuses": "Tutti gli stati",
"allPartners": "Tutti i partner",
"allAssignees": "Tutti gli assegnatari",
"allCategories": "Tutte le categorie",
"createdAt": "Creato",
"noTickets": "Nessun ticket trovato.",
"backToTickets": "Torna ai ticket",