unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Grids, DBGrids, StdCtrls,
  ExtCtrls, Vcl.DBCtrls, Vcl.Buttons, DB, Math;

type
  TfrmMain = class(TForm)
    tbcMain: TPageControl;
    tabReport: TTabSheet;
    tabUserManagement: TTabSheet;
    tabViewReports: TTabSheet;
    tabLogout: TTabSheet;
    dbGridUsers: TDBGrid;
    edtFullname: TEdit;
    edtSurname: TEdit;
    pnlNewUser: TPanel;
    btnDBnavUP: TBitBtn;
    btnDBnavDOWN: TBitBtn;
    rpgGender: TRadioGroup;
    btnAddUser: TBitBtn;
    lblNewUser: TLabel;
    edtEmail: TEdit;
    tabLogs: TTabSheet;
    tabDash: TTabSheet;
    lblUserDash: TLabel;
    redEvent: TRichEdit;
    btnUserDel: TBitBtn;
    BitBtn1: TBitBtn;
    btnEnabled: TBitBtn;
    btnClearLog: TBitBtn;
    lblUsernameUser: TLabel;
    pnlMyInfoUser: TPanel;
    lblFullnameUser: TLabel;
    lblSurnameUser: TLabel;
    lblCellUser: TLabel;
    lblEmailuser: TLabel;
    lblHeadMyInfoUser: TLabel;
    edtFullnameDashUser: TEdit;
    edtSurnameDashUser: TEdit;
    edtCellDashUser: TEdit;
    edtEmailDashUser: TEdit;
    btnUserUpdate: TBitBtn;
    btnChangePasswordUser: TBitBtn;
    rpgGenderDashUser: TRadioGroup;
    lblUsernameDashUser: TLabel;
    edtCellphone: TEdit;
    lblHeadEnabled: TLabel;
    lblEnabled: TLabel;
    pnlGridControls: TPanel;
    memReportBody: TMemo;
    edtReportTitle: TEdit;
    lblHeadReport: TLabel;
    btnSubmitReport: TBitBtn;
    cmbSelectUserReport: TComboBox;
    edtUsersInReport: TEdit;
    lblHeadReportUsers: TLabel;
    btnAddUSerToReport: TBitBtn;
    btnReportUsersClear: TBitBtn;
    btnReportClearAll: TBitBtn;
    btnSortAlphaAZ: TButton;
    lblHeadSort: TLabel;
    btnSortAlphaZA: TButton;
    Label1: TLabel;
    Label2: TLabel;
    cmbAvalibleReports: TComboBox;
    Label3: TLabel;
    lblHeadingViewReportsSelectReport: TLabel;
    btnLoad: TButton;
    redSelectedReport: TRichEdit;
    memWelcome: TMemo;
    Label4: TLabel;
    lblSalaries: TLabel;
    Procedure FormClose(Sender: TObject; var Action: TCloseAction);
    Procedure FormActivate(Sender: TObject);
    Procedure btnDBnavUPClick(Sender: TObject);
    Procedure btnDBnavDOWNClick(Sender: TObject);
    Procedure btnAddUserClick(Sender: TObject);
    Procedure addUser(sUsername, sPriv, sHashedPass : string; rSalary : real);
    Procedure genUsername(var sUsername : string);
    Procedure FormCreate(Sender: TObject);
    Procedure tbcMainChange(Sender: TObject);
    Procedure resetNewUser;
    Procedure loadEvents;
    procedure btnUserDelClick(Sender: TObject);
    procedure btnClearLogClick(Sender: TObject);
    Procedure loadUserDash;
    procedure btnUserUpdateClick(Sender: TObject);
    procedure btnEnabledClick(Sender: TObject);
    procedure updateEnabledLbl;
    procedure BitBtn1Click(Sender: TObject);
    procedure dbGridUsersColumnMoved(Sender: TObject; FromIndex, ToIndex: Integer);
    Procedure loadReport;
    procedure btnAddUSerToReportClick(Sender: TObject);
    procedure btnReportUsersClearClick(Sender: TObject);
    procedure btnSubmitReportClick(Sender: TObject);
    procedure loadUsers;
    procedure btnReportClearAllClick(Sender: TObject);
    procedure loadReports;
    procedure btnLoadClick(Sender: TObject);
    procedure btnSortAlphaAZClick(Sender: TObject);
    procedure btnSortAlphaZAClick(Sender: TObject);
    procedure loadUserManager;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

uses Login, DBUsers_u, util_u, Password, Help;

procedure TfrmMain.genUsername(var sUsername: string);
begin
  { | Generate new username and set it to the passed referance varable:
    | First character of the user's Fullname
    | Add surname without spaces
    | Add a Random number between 1000 and 9999
    }
  Randomize;
  sUsername := LowerCase(edtFullname.Text[1] + util.noSpace(edtSurname.Text)) + IntToStr(RandomRange(1000, 10000));
end;

// Show taskbar icon
procedure TfrmMain.CreateParams(var Params: TCreateParams);
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
end;

// Load events from event.log textfile and formate/display with styling in richedit
procedure TfrmMain.loadEvents;
Var
  tFile : Textfile;
  sLine, sDateTime, sType : string;
  iPos, iLen, iTotChar : integer;
  cType : char;
begin
  util.initFile('event.log', tFile);
  Reset(tFile);
  iTotChar := 1;
  while NOT(EOF(tFile)) do begin
    Readln(tFile, sLine);
    iLen := length(sLine)+2;
    iPos := Pos(';', sLine);
    sDateTime := Copy(sLine, 1, iPos-1);
    Delete(sLine, 1, iPos);
    ctype := sLine[3];
    iPos := Pos(';', sLine);
    sType := Copy(sLine, 1, iPos-1);
    Delete(sLine, 1, iPos);

    with redEvent do begin
      case cType of
        'E': begin
          selStart := iTotChar+1;
          selLength := iLen;
          selAttributes.Color := clGray;
          selText := sDateTime + ' ';
          selAttributes.Color := clRed;
          selText := sType + ' ';
          SelAttributes.Color := clWindowText;
          selText := sLine + #13#10;
          iTotChar := iTotChar + iLen;
        end;
        'I': begin
          selStart := iTotChar+1;
          selLength := iLen;
          selAttributes.Color := clGray;
          selText := sDateTime + ' ';
          selAttributes.Color := clBlue;
          selText := sType + ' ';
          SelAttributes.Color := clWindowText;
          selText := sLine + #13#10;
          iTotChar := iTotChar + iLen;
        end;
        'W': begin
          selStart := iTotChar+1;
          selLength := iLen;
          selAttributes.Color := clGray;
          selText := sDateTime + ' ';
          selAttributes.Color := clWebDarkOrange;
          selText := sType + ' ';
          SelAttributes.Color := clWindowText;
          selText := sLine + #13#10;
          iTotChar := iTotChar + iLen;
        end;
      end;
    end;
  end;
  CloseFile(tFile);
  //redEvent.lines.LoadFromFile(sTFname);
end;

// Loads tab where users report issues
procedure TfrmMain.loadReport;
begin
  memReportBody.Clear;
  edtReportTitle.Clear;

  loadUsers;
end;

// Loads tab where HR views reports
procedure TfrmMain.loadReports;
Var
  tFile : TextFile;
  sLine : string;
begin
  util.initFile('Reports.txt', tFile);
  Reset(tFile);

  cmbAvalibleReports.Items.Clear;
  cmbAvalibleReports.Text := '';
  cmbAvalibleReports.ItemIndex := -1;
  cmbAvalibleReports.AutoComplete := true;
  redSelectedReport.Clear;

  while NOT(EOF(tFile)) do begin
    Readln(tFile, sLine);
    if sLine <> '*-*-*-*' then    // loops until report begin marker is found
      continue;

    Readln(tFile, sLine);        // gets title from report text block and loops till next marker
    cmbAvalibleReports.Items.Append(sLine);
  end;

end;

// Load selected report from file
procedure TfrmMain.btnLoadClick(Sender: TObject);
Var
  tFile : TextFile;
  sLine, sSearchFor : string;
begin
if cmbAvalibleReports.Items.Count = 0 then
    util.error('No reports avalible', false)
  else if cmbAvalibleReports.ItemIndex = -1 then
    util.error('Please select a report from the list', false)
  else begin
    util.initFile('Reports.txt', tFile);
    Reset(tFile);

    redSelectedReport.Clear;
    sSearchFor := cmbAvalibleReports.Items[cmbAvalibleReports.ItemIndex];

    while NOT(EOF(tFile)) do begin
      Readln(tFile, sLine);
      if sLine <> '*-*-*-*' then    // loops until report begin marker is found
        continue;

      Readln(tFile, sLine);
      if sLine <> sSearchFor then  // Check if title matches what we search for
        continue;

      with redSelectedReport do begin
        Readln(tFile, sLine);  // Read who wrote it
        Lines.Add('Written by: ' + sLine);
        Readln(tFile, sLine);  // Read which users where involved
        Lines.Add('Users involved: ' + sLine + #13);
        Lines.Add('Report:');

      end;
      while NOT(EOF(tFile)) do begin
        readln(tFile, sLine);
        if sLine = '%-%-%-%' then begin
          CloseFile(tFile);
          exit;
        end;
        redSelectedReport.Lines.Add(sLine);
      end;
    end;
  end;
end;

// Display user personal information on tab load
procedure TfrmMain.loadUserDash;
begin
  with frmLogin.getUser do begin
    lblUsernameDashUser.Caption := username;
    edtFullnameDashUser.Text := fullname;
    edtSurnameDashUser.Text := surname;
    edtEmailDashUser.Text := email;
    edtCellDashUser.Text := cellphone;
    case gender of
      'M': rpgGenderDashUser.ItemIndex := 0;
      'F': rpgGenderDashUser.ItemIndex := 1;
      'N': rpgGenderDashUser.ItemIndex := -1;
    end;
  end;
end;

// Procedure to load users from database into a dynamic array of Tuser objects
procedure TfrmMain.loadUsers;
Var
  arrUsers : array of TUser;
  i : integer;
begin
  cmbSelectUserReport.Items.Clear;
  edtUsersInReport.Clear;

  SetLength(arrUsers, tblUsers.RecordCount);
  util.importUsers(arrUsers);
  for i := 0 to length(arrUsers)-1 do
      cmbSelectUserReport.Items.Append(arrUsers[i].fullname + ' ' + arrUsers[i].surname);
end;


// procedure to update user's personal information
procedure TfrmMain.btnUserUpdateClick(Sender: TObject);
Var
  newUser : TUser;
  sReason : string;
begin
  if util.validateUserNames(sReason, edtFullnameDashUser.Text, edtSurnameDashUser.Text) AND
     util.cellValid(sReason, edtCellDashUser.Text) then
  with newUser do begin
    username := frmLogin.getUser.username;
    fullname := edtFullnameDashUser.Text;
    surname := edtSurnameDashUSer.Text;
    email := edtEmailDashUser.Text;
    cellphone := edtCellDashUSer.Text;
    privilege := frmlogin.getUser.privilege;
    enabled := frmLogin.getUser.enabled;
      tblUsers.Open;
      tblUsers.Locate('Username', username, [loCaseInsensitive]);
      tblUserInfo.RecNo := tblUsers.RecNo;
      tblUsers.Edit;
      tblUserinfo.Edit;
        util.writeUser(newUser);
      tblUsers.Post;
      tblUserInfo.Post;
    util.logevent('User ' + username + ' was updated', TEventType.info);
    frmLogin.logout; // For security reasons we log out to refresh information
  end else
    util.error(sReason, false);
end;

// procedure to sync databases
procedure TfrmMain.dbGridUsersColumnMoved(Sender: TObject; FromIndex,
  ToIndex: Integer);
begin
  tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
end;

// Reset the add new user fields
procedure TfrmMain.resetNewUser;
begin
  edtFullname.Clear;
  edtSurname.Clear;
  edtEmail.Clear;
  edtCellphone.Clear;
  rpgGender.ItemIndex := -1;
  tbcMain.ActivePageIndex := 3;
end;


// Procedure to add a new user to the system
procedure TfrmMain.addUser(sUsername, sPriv, sHashedPass : string; rSalary : real);
Var
  newUser : util_u.TUser;
begin
  // First we write all the user data to a user object
  // and then we pass it to the helper procedure util.writeuser
  // that will write it to the database
  with newUser do begin
    username := sUsername;
    privilege := sPriv;
    fullname := edtFullname.Text;
    surname := edtSurname.Text;
    gender := rpgGender.Items[rpgGender.ItemIndex][1];
    email := edtEmail.Text;
    cellphone := edtCellphone.Text;
    enabled := true;
  end;

  // Write the user to the database
  tblUsers.Open;
  tblUserInfo.Open;
  tblUsers.Last;
  tblUserInfo.Last;
  tblUserInfo.Insert;
  tblUsers.Insert;
    util.writeUser(newUser);
    tblUsers['HashedPASS'] := sHashedPass;
    tblUserInfo['Salary'] := rSalary;
  tblUsers.Post;
  tblUserInfo.Post;
  Util.logevent('New user ' + sUsername + ' added.', TEventType.info); // Log event as info type (2)
  resetNewUser;
end;

// Shwo Help form
procedure TfrmMain.BitBtn1Click(Sender: TObject);
begin
  frmHelp.show;
end;

Procedure TfrmMain.btnAddUserClick(Sender: TObject);
Var
  sReason, sUsername, sHashedPass, sPriv : string;
begin
  // Pass referance to local varable for error responce
  if (util.validateUserNames(sReason, edtFullname.Text, edtSurname.Text)) AND
     (util.cellValid(sReason, edtCellphone.Text)) AND (util.checkGender(sReason, rpgGender.ItemIndex)) then begin
    genUsername(sUsername);
    util.getPriv(sPriv);
    if util.newPassword(sHashedPass, newPass) then
      addUser(sUsername, sPriv, sHashedPass, StrToFloat(InputBox('Salary','Enter the user'#27' salary:','0.0')));
  end else
    util.error('Invaild User information:'+ #13 + sReason, false);
end;

// Procedure to add users involved/mentioned in the message to the report
procedure TfrmMain.btnAddUSerToReportClick(Sender: TObject);
Var
  sSpacer, sUser, sEdit : string;
begin
  sSpacer := ', ';
  sEdit := edtUsersInReport.Text;
  sUser := cmbSelectUserReport.Items[cmbSelectUserReport.ItemIndex]; // Get currently selected user

  if cmbSelectUserReport.ItemIndex <> -1 then
    if Copy(sUser, 1, 7) <> '<added>' then begin // Check if user was already added
      if sEdit = '' then // Adds a comma only if there already exists a name
        sSpacer := '';
      cmbSelectUserReport.Text := sEdit + sSpacer + sUser;
      cmbSelectUserReport.Items[cmbSelectUserReport.ItemIndex] := '<added> ' + sUser;
    end else
      util.error('You already added this user, please select a diffrent one', false)
  else
    util.error('Please select a user', false);

  cmbSelectUserReport.ItemIndex := -1;
  cmbSelectUserReport.SetFocus;
end;

// Procedure to clear the event log and also log that it was cleared
procedure TfrmMain.btnClearLogClick(Sender: TObject);
Var
  tFile : TextFile;
begin
  if MessageDlg('Are you sure you want to clear the event log?', mtConfirmation, [mbYes,mbCancel], 2) = 6 then begin
    DeleteFile('event.log');
    Util.initFile('event.log', tFile);
    Util.logevent('Event log was cleared', TEventType.warning);
    CloseFile(tFile);
    redEvent.Clear;
  end;
end;

Procedure TfrmMain.FormActivate(Sender: TObject);
begin
  // Reset form and DB
  tbcMain.TabIndex := 0;
  dbGridUsers.datasource := dsrTbl;
  tblUsers.First;

  // Get privilege form login form and configure user interface acordingly
  if frmLogin.getUser.privilege = 'user' then with tbcMain do begin
    tbcMain.Pages[0].tabVisible := true; // Dashboard
    tbcMain.Pages[1].tabVisible := true; // Report
    tbcMain.Pages[2].tabVisible := false; // View Report
    tbcMain.Pages[3].tabVisible := false; // User management
    tbcMain.Pages[4].tabVisible := false; // Event logger
    loadUserDash;
  end else if frmLogin.getUser.privilege = 'HR' then with tbcMain do begin
    tbcMain.Pages[0].tabVisible := true; // Dashboard
    tbcMain.Pages[1].tabVisible := true; // Report
    tbcMain.Pages[2].tabVisible := true; // View Report
    tbcMain.Pages[3].tabVisible := true; // User management
    tbcMain.Pages[4].tabVisible := false; // Event logger
    loadUserDash;
  end else if frmLogin.getUser.privilege = 'admin' then with tbcMain do begin
    tbcMain.Pages[0].tabVisible := true; // Dashboard
    tbcMain.Pages[1].tabVisible := true; // Report
    tbcMain.Pages[2].tabVisible := true; // View Report
    tbcMain.Pages[3].tabVisible := true; // User management
    tbcMain.Pages[4].tabVisible := true; // Event logger
    loadUserDash;
  end;
end;

// Close application
Procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 Application.Terminate;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  // Set max input length for edt boxes (equal to DB field limit)
  edtFullName.MaxLength := 25;
  edtSurname.MaxLength := 25;
  edtEmail.MaxLength := 30;
  edtCellDashUser.MaxLength := 11;
  edtCellphone.MaxLength := 11;
  edtFullnameDashUser.MaxLength := 25;
  edtSurnameDashUSer.MaxLength := 25;
  edtEmailDashUSer.MaxLength := 30;
end;

procedure TfrmMain.tbcMainChange(Sender: TObject);
begin
  // Tab loading procedures triggered on the tab change events
  case tbcMain.ActivePageIndex of
    0: loadUserDash;
    1: loadReport;
    2: loadReports;
    3: loadUserManager;
    4: loadEvents;
    5: Login.frmLogin.logout; // Log user out on tab logout click
  end;
end;

// Load usermanager tab
procedure TfrmMain.loadUserManager;
Var
  rTotal : Real;
begin
  updateEnabledLbl;
  // calculate total salary

  with tblUserInfo do begin
    Open;
    First;
    while NOT(tblUserInfo.Eof) do begin
      if tblUSerInfo['Salary'] <> Null then
        rTotal := rTotal + tblUserInfo['Salary'];
      tblUserInfo.Next;
    end;
  end;
  lblSalaries.Caption := FormatFloat('R0.00', rTotal);
  tblUserInfo.First; // Sync indexs
  tblUsers.First;
end;

// Helper procedure to update enabled label state
procedure TfrmMain.updateEnabledLbl;
begin
 if tblUsers['Enabled'] = True then
    lblEnabled.Caption := 'Enabled'
  else
    lblEnabled.Caption := 'Disabled';
end;

// navigation button
Procedure TfrmMain.btnDBnavDOWNClick(Sender: TObject);
begin
  tblUserInfo.Next;
  tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
  updateEnabledLbl;
end;

// navigation button
Procedure TfrmMain.btnDBnavUPClick(Sender: TObject);
begin
  tblUserInfo.Prior;
  tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
  updateEnabledLbl;
end;

// Handel if the admin wants to disable/enable user and update database
procedure TfrmMain.btnEnabledClick(Sender: TObject);
begin
  if NOT(tblUsers['Username'] = 'admin') then
    if NOT(tblUsers['Username'] = frmLogin.getUser.username) then begin
    tblUsers.Open;
    tblUsers.Edit;
    if tblUsers['Enabled'] = True then
      tblUsers['Enabled'] := False
    else
      tblUsers['Enabled'] := True;
    tblUsers.Post;
    updateEnabledLbl;
    end else
      util.error('You cannot disable your own user', false)
  else
    util.error('You cannot disable the admin user', false);
end;

// Button to reset the whole tab
procedure TfrmMain.btnReportClearAllClick(Sender: TObject);
begin
  loadReport;
end;

// Button to reset the users involved section of the tab
procedure TfrmMain.btnReportUsersClearClick(Sender: TObject);
begin
  loadUsers;
end;

// Button to sort the database
procedure TfrmMain.btnSortAlphaAZClick(Sender: TObject);
begin
  tblUsers.Sort := 'Username ASC';
  tblUserInfo.Sort := 'Username ASC';
  tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
end;

// Button to sort the database
procedure TfrmMain.btnSortAlphaZAClick(Sender: TObject);
begin
  tblUsers.Sort := 'Username DESC';
  tblUserInfo.Sort := 'Username DESC';
  tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
end;

// Procedure to question the user for confirmation and
// then write the report to the textfile
procedure TfrmMain.btnSubmitReportClick(Sender: TObject);
Var
  tFile : TextFile;
  i : integer;
begin
  if MessageDlg('Are you sure you want to submit your report?', mtConfirmation, [mbYes,mbCancel], 2) <> 6 then
    exit;

  util.initFile('Reports.txt', tFile);
  Append(tFile);
    Writeln(tFile, '*-*-*-*');
    Writeln(tFile, edtReportTitle.Text); // Add title
    Writeln(tFile, frmLogin.getUser.fullname + ' ' + frmLogin.getUser.surname);   // Add current user's name
    Writeln(tFile, edtUsersInReport.Text);
    for i := 0 to memReportBody.Lines.Count do
      Writeln(tFile, memReportBody.Lines[i]{.ValueFromIndex[i]} );
    Writeln(tFile, '%-%-%-%');
  CloseFile(tFile);

  MessageDlg('Your report have been successfully submited.', mtInformation, [mbOK], 2);
  util.logevent('User ' + frmlogin.getUser.username + ' created a report', TEventType.info);

  loadReport;
end;

procedure TfrmMain.btnUserDelClick(Sender: TObject);
begin
  if MessageDlg('Are you sure you want to delete the user?', mtConfirmation, [mbYes,mbCancel], 2) <> 6 then
    exit;
  // Check if admin user is selected and throw error
  if tblUserInfo['Username'] = 'admin' then
    Util.error('You cannot delete the admin user', false)
  else if tblUserInfo['Username'] = frmLogin.getUser.username then
    Util.error('You cannot delete your own user', false)
  else begin
    tblUsers.RecNo := tblUserInfo.RecNo; // Sync index
    util.logevent('User ' + tblUserInfo['Username'] + ' was deleted', TEventType.warning);
    tblUserInfo.Delete;
    tblUsers.Delete;
  end;

end;

end.
