function getDashboard(
  token,
  fromDate,
  toDate
) {

  const user =
    requireUser_(
      token
    );


  if (
    !user.employee_id
  ) {

    return getRoleDashboard_(
      user
    );

  }


  /* =========================================
     ZAMĚSTNANCI
  ========================================= */

  const employees =
    displayRows_(
      CFG.SHEETS.EMPLOYEES
    );


  const employee =
    employees.find(
      function(item) {

        return (
          String(
            item.employee_id
          ) ===
          String(
            user.employee_id
          )
        );

      }
    );


  if (
    !employee
  ) {

    throw new Error(
      'Zaměstnanec nebyl nalezen.'
    );

  }


  /* =========================================
     DATUMOVÝ ROZSAH
  ========================================= */

  const now =
    new Date();


  let from;


  if (
    fromDate
  ) {

    from =
      new Date(
        fromDate +
        'T00:00:00'
      );

  } else {

    from =
      new Date(
        now.getFullYear(),
        now.getMonth(),
        1,
        0,
        0,
        0
      );

  }


  let to;


  if (
    toDate
  ) {

    to =
      new Date(
        toDate +
        'T23:59:59'
      );

  } else {

    to =
      now;

  }


  /* =========================================
     SMĚNY
  ========================================= */

  const allShifts =
    displayRows_(
      CFG.SHEETS.SHIFTS
    );


  const employeeShifts =
    allShifts
      .filter(
        function(shift) {

          return (
            String(
              shift.employee_id
            ) ===
            String(
              employee.employee_id
            )
          );

        }
      )
      .filter(
        function(shift) {

          const clockIn =
            parseEmployeeDate_(
              shift.clock_in
            );


          if (
            !clockIn
          ) {

            return false;

          }


          return (
            clockIn >= from &&
            clockIn <= to
          );

        }
      )
      .sort(
        function(a,b) {

          const aDate =
            parseEmployeeDate_(
              a.clock_in
            );


          const bDate =
            parseEmployeeDate_(
              b.clock_in
            );


          return (
            (
              bDate
                ? bDate.getTime()
                : 0
            )
            -
            (
              aDate
                ? aDate.getTime()
                : 0
            )
          );

        }
      );


  /* =========================================
     SAZBY
  ========================================= */

  const payRates =
    displayRows_(
      CFG.SHEETS.PAY_RATES
    );


  /* =========================================
     VÝPOČET HODIN + PENĚZ
  ========================================= */

  let workedMinutes =
    0;


  let earnedEstimate =
    0;


  let shiftCount =
    0;


  employeeShifts.forEach(
    function(shift) {

      const status =
        String(
          shift.status || ''
        ).trim();


      /*
       * Do výdělku počítáme pouze
       * dokončené směny.
       */

      if (
        ![
          'COMPLETED',
          'EMPLOYEE_CONFIRMED',
          'MANAGER_APPROVED',
          'LOCKED'
        ].includes(
          status
        )
      ) {

        return;

      }


      const minutes =
        numberFromSheet_(
          shift.worked_minutes
        );


      if (
        minutes <= 0
      ) {

        return;

      }


      const shiftDate =
        parseEmployeeDate_(
          shift.clock_in
        );


      const rate =
        employeeRateAt_(
          payRates,
          employee.employee_id,
          shiftDate
        );


      workedMinutes +=
        minutes;


      shiftCount++;


      earnedEstimate +=
        (
          minutes /
          60
        ) *
        rate;

    }
  );


  /* =========================================
     OTEVŘENÁ SMĚNA
  ========================================= */

  const openShift =
    allShifts
      .filter(
        function(shift) {

          return (
            String(
              shift.employee_id
            ) ===
            String(
              employee.employee_id
            ) &&
            String(
              shift.status
            ).trim() ===
            'OPEN'
          );

        }
      )
      .sort(
        function(a,b) {

          const aDate =
            parseEmployeeDate_(
              a.clock_in
            );


          const bDate =
            parseEmployeeDate_(
              b.clock_in
            );


          return (
            (
              bDate
                ? bDate.getTime()
                : 0
            )
            -
            (
              aDate
                ? aDate.getTime()
                : 0
            )
          );

        }
      )[0] || null;


  let currentShiftMinutes =
    0;


  if (
    openShift
  ) {

    const clockIn =
      parseEmployeeDate_(
        openShift.clock_in
      );


    if (
      clockIn
    ) {

      currentShiftMinutes =
        Math.max(
          0,
          Math.floor(
            (
              Date.now() -
              clockIn.getTime()
            ) /
            60000
          )
        );

    }

  }


  /* =========================================
     AKTUALITY
  ========================================= */

  let news =
    [];


  try {

    news =
      getNewsForUser_(
        user,
        employee
      );


    /*
     * Bezpečný převod pro frontend.
     */

    news =
      news
        .slice(
          0,
          8
        )
        .map(
          function(item) {

            return {

              news_id:
                String(
                  item.news_id || ''
                ),

              title:
                String(
                  item.title || ''
                ),

              content:
                String(
                  item.content || ''
                ),

              category:
                String(
                  item.category || ''
                ),

              require_confirmation:
                item.require_confirmation === true ||
                String(
                  item.require_confirmation
                ).toUpperCase() ===
                'TRUE'

            };

          }
        );

  } catch(error) {

    news =
      [];

  }


  /* =========================================
     BEZPEČNÝ SEZNAM SMĚN
  ========================================= */

  const safeShifts =
    employeeShifts
      .slice(
        0,
        20
      )
      .map(
        function(shift) {

          const clockIn =
            parseEmployeeDate_(
              shift.clock_in
            );


          const clockOut =
            parseEmployeeDate_(
              shift.clock_out
            );


          return {

            shift_id:
              String(
                shift.shift_id || ''
              ),

            employee_id:
              String(
                shift.employee_id || ''
              ),

            work_date:
              String(
                shift.work_date || ''
              ),

            clock_in:
              clockIn
                ? clockIn.toISOString()
                : '',

            clock_out:
              clockOut
                ? clockOut.toISOString()
                : '',

            break_minutes:
              numberFromSheet_(
                shift.break_minutes
              ),

            worked_minutes:
              numberFromSheet_(
                shift.worked_minutes
              ),

            status:
              String(
                shift.status || ''
              ),

            location_id:
              String(
                shift.location_id || ''
              )

          };

        }
      );


  /* =========================================
     DEBUG INFO

     Zatím schválně vracíme i sazbu,
     ať přesně vidíme, co systém používá.
  ========================================= */

  const currentRate =
    employeeRateAt_(
      payRates,
      employee.employee_id,
      now
    );


  /* =========================================
     VÝSLEDEK
  ========================================= */

  return {

    role:
      String(
        user.role || ''
      ),


    employee: {

      employee_id:
        String(
          employee.employee_id || ''
        ),

      name:
        (
          String(
            employee.first_name || ''
          ) +
          ' ' +
          String(
            employee.last_name || ''
          )
        ).trim(),

      position:
        String(
          employee.position || ''
        )

    },


    stats: {

      worked_minutes:
        Math.round(
          workedMinutes
        ),

      earned_estimate:
        Math.round(
          earnedEstimate
        ),

      shift_count:
        shiftCount,

      current_shift_minutes:
        currentShiftMinutes,

      hourly_rate:
        currentRate

    },


    open_shift:
      openShift
        ? {

            shift_id:
              String(
                openShift.shift_id || ''
              ),

            clock_in:
              parseEmployeeDate_(
                openShift.clock_in
              )
                ? parseEmployeeDate_(
                    openShift.clock_in
                  ).toISOString()
                : '',

            status:
              'OPEN'

          }
        : null,


    shifts:
      safeShifts,


    schedules:
      [],


    news:
      news

  };

}



/* ============================================================
   SAZBA PRO KONKRÉTNÍ DATUM
============================================================ */

function employeeRateAt_(
  rates,
  employeeId,
  targetDate
) {

  if (
    !targetDate
  ) {

    return 0;

  }


  const target =
    Utilities.formatDate(
      targetDate,
      CFG.TZ,
      'yyyy-MM-dd'
    );


  const employeeRates =
    rates
      .filter(
        function(rate) {

          return (
            String(
              rate.employee_id
            ) ===
            String(
              employeeId
            )
          );

        }
      )
      .filter(
        function(rate) {

          const fromDate =
            parseEmployeeDate_(
              rate.valid_from
            );


          const toDate =
            parseEmployeeDate_(
              rate.valid_to
            );


          const from =
            fromDate
              ? Utilities.formatDate(
                  fromDate,
                  CFG.TZ,
                  'yyyy-MM-dd'
                )
              : '0000-00-00';


          const to =
            toDate
              ? Utilities.formatDate(
                  toDate,
                  CFG.TZ,
                  'yyyy-MM-dd'
                )
              : '9999-12-31';


          return (
            from <= target &&
            to >= target
          );

        }
      );


  if (
    !employeeRates.length
  ) {

    return 0;

  }


  employeeRates.sort(
    function(a,b) {

      const aDate =
        parseEmployeeDate_(
          a.valid_from
        );


      const bDate =
        parseEmployeeDate_(
          b.valid_from
        );


      return (
        (
          bDate
            ? bDate.getTime()
            : 0
        )
        -
        (
          aDate
            ? aDate.getTime()
            : 0
        )
      );

    }
  );


  return numberFromSheet_(
    employeeRates[0]
      .hourly_rate
  );

}



/* ============================================================
   DATUM Z GOOGLE SHEETS
============================================================ */

function parseEmployeeDate_(
  value
) {

  const text =
    String(
      value || ''
    ).trim();


  if (
    !text
  ) {

    return null;

  }


  let match;


  /*
   * 2026-08-11
   * 2026-08-11 23:45:00
   */

  match =
    text.match(
      /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[1]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[3]
      ),
      Number(
        match[4] || 0
      ),
      Number(
        match[5] || 0
      ),
      Number(
        match[6] || 0
      )
    );

  }


  /*
   * 11.8.2026 23:45:00
   */

  match =
    text.match(
      /^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[3]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[1]
      ),
      Number(
        match[4] || 0
      ),
      Number(
        match[5] || 0
      ),
      Number(
        match[6] || 0
      )
    );

  }


  /*
   * 8/11/2026 23:45:00
   */

  match =
    text.match(
      /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[3]
      ),
      Number(
        match[1]
      ) - 1,
      Number(
        match[2]
      ),
      Number(
        match[4] || 0
      ),
      Number(
        match[5] || 0
      ),
      Number(
        match[6] || 0
      )
    );

  }


  const date =
    new Date(
      text
    );


  if (
    isNaN(
      date.getTime()
    )
  ) {

    return null;

  }


  return date;

}



/* ============================================================
   ROLE DASHBOARD
============================================================ */

function getRoleDashboard_(
  user
) {

  return {

    role:
      String(
        user.role || ''
      )

  };

}



/* ============================================================
   TEST VÝDĚLKU

   Spustitelné přímo z editoru.
============================================================ */

function testEmployeeEarnings() {

  const employees =
    displayRows_(
      CFG.SHEETS.EMPLOYEES
    );


  const shifts =
    displayRows_(
      CFG.SHEETS.SHIFTS
    );


  const rates =
    displayRows_(
      CFG.SHEETS.PAY_RATES
    );


  employees.forEach(
    function(employee) {

      const employeeId =
        String(
          employee.employee_id
        );


      const employeeShifts =
        shifts.filter(
          function(shift) {

            return (
              String(
                shift.employee_id
              ) ===
              employeeId
            );

          }
        );


      Logger.log(
        '--------------------------------'
      );


      Logger.log(
        'ZAMĚSTNANEC: ' +
        employee.first_name +
        ' ' +
        employee.last_name
      );


      employeeShifts.forEach(
        function(shift) {

          const date =
            parseEmployeeDate_(
              shift.clock_in
            );


          const rate =
            employeeRateAt_(
              rates,
              employeeId,
              date
            );


          const minutes =
            numberFromSheet_(
              shift.worked_minutes
            );


          Logger.log(
            'SMĚNA: ' +
            shift.shift_id +
            ' | stav=' +
            shift.status +
            ' | minuty=' +
            minutes +
            ' | sazba=' +
            rate +
            ' | výdělek=' +
            (
              minutes /
              60 *
              rate
            )
          );

        }
      );

    }
  );

}