add assignee and category filter to ticket system
This commit is contained in:
parent
c5678b0856
commit
1bda67fec6
|
|
@ -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
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
|
|
@ -267,6 +267,8 @@ export type TicketSummary = {
|
|||
distributionPartner: string;
|
||||
customSubCategory: string | null;
|
||||
customCategory: string | null;
|
||||
assigneeId: number | null;
|
||||
assigneeName: string | null;
|
||||
};
|
||||
|
||||
export type AdminUser = {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in New Issue