3. Řešení úkolu

Třída Zaměstnanec

namespace kolekce_objektu_filtrovani.Models
{
    public class Zamestnanec
    {
        public int Id { get; set; }
        public string Jmeno { get; set; } = "";
        public string Oddeleni { get; set; } = "";
        public double Plat { get; set; }
    }
}

Třída Zaměstnanec slouží jako datový model. Definuje vlastnosti Id, Jméno, Oddělení a Plat, které musí mít každý evidovaný pracovník. Instance této třídy budeme ukládat do seznamu (kolekce).


Třída Oddělení

namespace kolekce_objektu_filtrovani.Models
{
    public class Oddeleni
    {
        public string NazevOddeleni { get; set; } = "";
        public int PocetZamestnancu { get; set; }
        public double CelkoveNaklady { get; set; }
    }
}

Třída Oddělení slouží jako pomocný datový model pro uložení výsledků našeho seskupování (pomocí metody GroupBy a Select). Definuje vlastnosti Název oddělení, Počet zaměstnanců a Celkové náklady, do kterých budeme ukládat vypočítané souhrnné statistiky k následnému vypsání do tabulky.


Razor komponenta

@page "/"
@using kolekce_objektu_filtrovani.Models

<PageTitle>Home</PageTitle>

<h3>Personální Dashboard - Analýza dat</h3>

<div class="row text-center mb-4">
    <div class="col-md-3">
        <div class="card bg-light p-3">
            <h6>Počet zaměstnanců</h6>
            <h4>@CelkovyPocet</h4> </div>
    </div>
    <div class="col-md-3">
        <div class="card bg-light p-3">
            <h6>Mzdové náklady (Měsíc)</h6>
            <h4>@CelkoveMzdoveNaklady.ToString("C0")</h4> </div> <!-- C0 udává měnu (C = koruny) a počet desetinných míst (v našem případě 0) -->
    </div>
    <div class="col-md-3">
        <div class="card bg-light p-3">
            <h6>Průměrný plat</h6>
            <h4>@PrumernyPlat.ToString("C0")</h4>
        </div>
    </div>
    <div class="col-md-3">
        <div class="card bg-light p-3">
            <h6>Nejvyšší plat</h6>
            <h4>@NejvyssiPlat.ToString("C0")</h4>
        </div>
    </div>
</div>

<div class="row">
    <div class="col-md-6 mb-3">
        <div class="card p-3 h-100">
            <h5>Nadstandardně placení (Nad 60k)</h5>
            <p>Seřazeno od nejlépe placeného (Where + OrderBy)</p>
            <ul>
                @foreach (var z in DobrePlaceni) {
                    <li>@z.Jmeno - @z.Plat.ToString("C0")</li> }
            </ul>

            <hr />
            <h5>Pouze jména zaměstnanců (Select)</h5>
            <p>@string.Join(", ", SeznamJmen)</p> </div> <!-- Join(", "); vypíše všechny položky v kolekci a oddělí je čárkou -->
    </div>

    <div class="col-md-6 mb-3">
        <div class="card p-3 h-100">
            <h5>Náklady podle oddělení (GroupBy + Select)</h5>
            <table class="table table-sm mt-3">
                <thead>
                    <tr>
                        <th>Oddělení</th>
                        <th>Počet lidí</th>
                        <th>Celkové platy</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var stat in StatistikyPodleOddeleni)
                    {
                        <tr>
                            <td><strong>@stat.NazevOddeleni</strong></td>
                            <td>@stat.PocetZamestnancu</td>
                            <td>@stat.CelkoveNaklady.ToString("C0")</td>
                        </tr>
                    }
                </tbody>
            </table>

            <hr />
            <h5>Hledání (FirstOrDefault & Any)</h5>
            <p>
                <strong>Zaměstnanec s ID 3:</strong> 
                @(HledanyZamestnanec != null ? HledanyZamestnanec.Jmeno : "Nenalezen") </p>
            <p>
                <strong>Máme zaměstnance s platem nad 100k?</strong> 
                @(VysokyPlat ? "Ano" : "Ne") </p>
        </div>
    </div>
</div>

@code {
    private List<Zamestnanec> zamestnanci = new List<Zamestnanec> // Zdrojová kolekce zaměstnanců
    {
        new Zamestnanec { Id = 1, Jmeno = "Jan Novák", Oddeleni = "IT", Plat = 75000 },
        new Zamestnanec { Id = 2, Jmeno = "Eva Malá", Oddeleni = "HR", Plat = 45000 },
        new Zamestnanec { Id = 3, Jmeno = "Petr Svoboda", Oddeleni = "IT", Plat = 82000 },
        new Zamestnanec { Id = 4, Jmeno = "František Dvořák", Oddeleni = "Marketing", Plat = 55000 },
        new Zamestnanec { Id = 5, Jmeno = "Karel Šmíd", Oddeleni = "Management", Plat = 120000 }
    };

    private int CelkovyPocet => zamestnanci.Count(); // .Count() spočítá všechny prvky v seznamu
    private double CelkoveMzdoveNaklady => zamestnanci.Sum(z => z.Plat); // .Sum() projde zaměstnance a sečte jejich platy
    private double PrumernyPlat => zamestnanci.Average(z => z.Plat); // .Average() vypočítá aritmetický průměr platů
    private double NejvyssiPlat => zamestnanci.Max(z => z.Plat); // .Max() najde absolutně nejvyšší hodnotu platu
    
    private bool VysokyPlat => zamestnanci.Any(z => z.Plat > 100000); // .Any() vrací true, pokud alespoň jeden splní podmínku

    private Zamestnanec HledanyZamestnanec => zamestnanci.FirstOrDefault(z => z.Id == 3); // .FirstOrDefault() najde prvního s ID 3, jinak vrátí null

    private List<Zamestnanec> DobrePlaceni => zamestnanci.Where(z => z.Plat >= 60000).OrderByDescending(z => z.Plat).ToList(); // .Where() vyfiltruje data a .OrderByDescending() je seřadí sestupně

    private List<string> SeznamJmen => zamestnanci.Select(z => z.Jmeno).ToList(); // .Select() extrahuje z objektů pouze vlastnost Jméno (vytvoří seznam textů)

    private List<Oddeleni> StatistikyPodleOddeleni => zamestnanci
        .GroupBy(z => z.Oddeleni) // Rozdělí lidi do skupin (IT, HR, Management...)
        .Select(skupina => new Oddeleni // Z každé skupiny vytvoří jeden statistický objekt
        {
            NazevOddeleni = skupina.Key, // .Key obsahuje to, podle čeho se seskupovalo (název)
            PocetZamestnancu = skupina.Count(), // Spočítá lidi uvnitř dané skupiny
            CelkoveNaklady = skupina.Sum(z => z.Plat) // Sečte platy pouze lidem v dané skupině
        })
        .ToList(); // Výsledek převede na klasický List
}

V části @code se nachází zdrojová kolekce zamestnanci naplněná daty. Agregační metody (např. .Count(), .Sum(), .Average()) provádějí matematické výpočty z celé sady dat a vracejí jedinou hodnotu.

Pro zápis podmínek uvnitř metod používáme tzv. lambda výrazy (např. z => z.Plat). Písmeno z si volíme sami a funguje jako zástupný název pro jednoho konkrétního zaměstnance, kterého příkaz zrovna kontroluje. Šipka => pak programu přesně říká, s jakou vlastností tohoto zaměstnance chceme pracovat.

.Where(z => z.Plat >= 60000) nejprve vybere pouze zaměstnance splňující zadanou podmínku (plat nad 60 000). Na tento zredukovaný výsledek se aplikuje .OrderByDescending(), která data seřadí sestupně. A nakonec metoda .ToList() uloží do klasického seznamu.

.Select(z => z.Jmeno) projde všechny záznamy a vytáhne z nich jen to, co jsme šipkou určili ("z každého zaměstnance z vyber jen jeho jméno"). Místo složitých objektů tak získáme jednoduchý seznam textů (List<string>).

.GroupBy(z => z.Oddeleni) rozdělí lidi do menších skupin podle oddělení. Navazující .Select(skupina => new Oddeleni {...}) pak už nepracuje s jednotlivci, ale vezme rovnou celou vzniklou skupinu a vytvoří z ní jeden nový souhrnný objekt. Přitom se pomocí .Count() a .Sum() rovnou vypočítají statistiky, které se izolovaně aplikují pouze na lidi uvnitř právě zpracovávané skupiny.