#!/usr/bin/perl -w
#!C:/cygwin/bin/perl.exe -w
#*****************************************************************************
#
# Filename: WorkoutTracker.pl
#
# CVS Revision: BS::Revision: 1427 $
#
# Description:  This perl CGI script reads the EXERCISE logs databases.
#       It is secured by using the REMOTE_USER environment variable.
#
#*****************************************************************************

# QUICK! send browser something always and define custom message
use CGI::Carp qw(fatalsToBrowser set_message);

BEGIN {
  sub handle_errors {
    my $error = shift;

    print "<h2>Software Logic Error:</h2>
<pre>$error</pre><br>
<h1>Please Contact the Web Administrator</h1>";
  }
  set_message(\&handle_errors);
}


#*****************************************************************************
#
# initialize variables
#
#*****************************************************************************
# compute arithmetic in integer instead of double
##use integer;
# restrict unsafe constructs
use strict;
# access mysql database
use DBI;
# program name
my @Program = split("/", $ENV{SCRIPT_NAME});
$API::program = $Program[$#Program];
my $here = $API::program.'::main';
# diagnostic level
$API::diag = 0;
# secure my PATH, required by taint mode
$ENV{PATH} = "/usr/local/bin:/usr/bin";

my $id = '$Id: WorkoutTracker 1445 2016-02-01 15:06:44Z c.schneider $';
my @Id = split(/ /, $id);
$API::revision = '2.'.$Id[2];
$www::db_logs_table = 'elogs';

$API::subtitle = 'Workouts';
$API::title = 'Race Calendar';
$www::HTML{Appl_CSS}  = "/css/Bootstrap_WorkoutTracker.css";
$www::HTML{Appl_JS}   = "/Bootstrap_WorkoutTracker.js";

$www::Default{WeekOrder} = 'Athlete,Date,ID';

# From Google
# 1 miles = 1.609344 kilometers
$My::Mile{km} = 1.609344;
# 1 miles = 1.609344 kileometers (20 * 50 meter laps)
$My::Mile{lap_50m} = 1.609344 * 20;
# 1 mile = 1760 yards (35.2 * 50 yard laps)
$My::Mile{lap_50yd} = 35.2;

#
# MET (Metabolic Equivalent): The ratio of the work metabolic rate to the resting
#    metabolic rate. One MET is defined as 1 kcal/kg/hour and is roughly equivalent
#    to the energy cost of sitting quietly. A MET also is defined as oxygen uptake
#    in ml/kg/min with one MET equal to the oxygen cost of sitting quietly, equivalent
#    to 3.5 ml/kg/min
#
# MET table from:
# http://prevention.sph.sc.edu/tools/compendium.htm
#
#
# 01020  6.0 bicycling, 10-11.9 mph, leisure, slow, light effort
# 01030  8.0 bicycling, 12-13.9 mph, leisure, moderate effort
# 01040 10.0 bicycling, 14-15.9 mph, racing or leisure, fast, vigorous effor
# 01050 12.0 bicycling, 16-19 mph, racing/not drafting or >19 mph drafting, very fast, racing general
# 01060 16.0 bicycling bicycling, >20 mph, racing, not drafting
# 12030  8.0 running running,  5 mph (12 min/mile)
# 12120 16.0 running running, 10 mph ( 6 min/mile)
# 18230 10.0 swimming laps, freestyle, fast, vigorous effort
# 18240  7.0 swimming laps, freestyle, slow, moderate or light effort
# 18250  7.0 swimming, backstroke, general
# 18260 10.0 swimming, breaststroke, general
# 18270 11.0 swimming, butterfly, general
# 1 mile = 1760 yards
# 18290  8.0 swimming, crawl, slow (50 yards/minute), moderate or light effort
#            50 yards/minute = 50 * 60 = 3000 yards/hour = 1.7045 miles/hour
# 18280 11.0 swimming, crawl, fast (75 yards/minute), vigorous effort
#            75 yards/minute = 75 * 60 = 4500 yards/hour = 2.5568 miles/hour

# set the applications prefs
$www::My_Prefs{elogsCalendar} = 'Leg:WorkOut:Miles';
$www::Prefs_Defaults{elogsCalendar} = 'Leg:WorkOut:Miles';
$www::Prefs_Labels{elogsCalendar} = 'Calendar Display Fields';
$www::elogsCalendar{'Leg:WorkOut:Miles'} = 'Leg, WorkOut, Miles Default';
$www::elogsCalendar{'WorkOut:Leg,Miles'} = 'WorkOut, Leg, Miles';
$www::elogsCalendar{'Leg:Miles'} = 'Leg, Equipment, Miles';
$www::elogsCalendar{'Leg:Miles'} = 'Equipment, Leg, Miles';
$www::elogsCalendar{'Leg:Miles:Time'} = 'Leg, Miles, Time';
$www::elogsCalendar{'Leg:Miles:MinMile'} = 'Leg, Miles, Pace';
$www::elogsCalendar{'Miles:Time:MinMile'} = 'Miles, Time, Pace';

$www::My_Prefs{elogsUnits} = 'MPH';
$www::Prefs_Defaults{elogsUnits} = 'MPH';
$www::Prefs_Labels{elogsUnits} = 'Distance units';
$www::elogsUnits{MPH} = 'Miles and MPH';
$www::elogsUnits{KPH} = 'Kilometers and KPH';

$www::My_Prefs{elogsTemp} = 'F';
$www::Prefs_Defaults{elogsTemp} = 'F';
$www::Prefs_Labels{elogsTemp} = 'Temperature units';
$www::elogsTemp{F} = 'Fahrenheit';
$www::elogsTemp{C} = 'Celsius';

$www::My_Prefs{elogsWX} = 'W';
$www::Prefs_Defaults{elogsWX} = 'W';
$www::Prefs_Labels{elogsWX} = 'Weather Site';
$www::elogsWX{A} = 'AccuWeather';
$www::elogsWX{W} = 'Weather Underground';
$www::elogsWX{T} = 'The Weather Channel';
$www::elogsWX{M} = 'MSN';
$www::elogsWX{N} = 'None';

$www::My_Prefs{elogsMgmt} = 'G';
$www::Prefs_Defaults{elogsMgmt} = 'G';
$www::Prefs_Labels{elogsMgmt} = 'Management View';
$www::elogsMgmt{A} = 'Admin';
$www::elogsMgmt{G} = 'Guest';

$www::My_Prefs{elogsHome} = 'U';
$www::Prefs_Defaults{elogsHome} = 'M';
$www::Prefs_Labels{elogsHome} = 'Home View';
$www::elogsHome{U} = 'Upcoming Races';
$www::elogsHome{C} = 'Carousel';
$www::elogsHome{M} = 'Month';
$www::elogsHome{W} = 'Week';

$www::My_Prefs{elogsImages} = 'Race';
$www::Prefs_Defaults{elogsImages} = 'Race';
$www::Prefs_Labels{elogsImages} = 'View Images';

my $year = (localtime)[5]+1900;
$www::Custom{Upcoming_Races} = "ListBy=GrEqDate:Leg:Athlete&OrderBy=Date&Show=TTT&Hide=Leg,Time,MPH,MinMile,Temp&Leg=Race&Athlete=-__DELETED__";
$www::Custom{Race_Calendar} = "ListBy=Athlete:Leg:Year&OrderBy=Date&Show=TTT&Hide=Eqmt,Time,MPH,MinMile,Temp&Leg=Race&Year=$year";

$www::My_Prefs{elogsCH} = '51';
$www::Prefs_Defaults{elogsCH} = '51';
$www::Prefs_Labels{elogsCH} = 'Carousel Height';
$www::elogsCH{11} = 'Small';
$www::elogsCH{23} = 'Default';
$www::elogsCH{31} = 'Larger';
$www::elogsCH{51} = 'Largest';

$www::My_Prefs{elogsIH} = 'D';
$www::Prefs_Defaults{elogsIH} = 'D';
$www::Prefs_Labels{elogsIH} = 'Carousel Image Hover';
$www::elogsIH{F} = 'Full-Size';
$www::elogsIH{D} = 'Disabled';

$www::My_Prefs{elogsCI} = '5';
$www::Prefs_Defaults{elogsCI} = '5';
$www::Prefs_Labels{elogsCI} = 'Carousel Image Count';
$www::elogsCI{3} = 'Fewest, 3';
$www::elogsCI{5} = 'Default, 5';
$www::elogsCI{7} = 'More, 7';
$www::elogsCI{9} = 'Most, 9';

$www::My_Prefs{elogsBlog} = '5';
$www::Prefs_Defaults{elogsBlog} = '5';
$www::Prefs_Labels{elogsBlog} = 'Blog Lines';
$www::elogsBlog{-1} = 'None';
$www::elogsBlog{03} = 'Few, 3';
$www::elogsBlog{05} = 'Default, 5';
$www::elogsBlog{07} = 'Several, 7';
$www::elogsBlog{11} = 'Many, 11';
$www::elogsBlog{23} = 'Page, 23';
$www::elogsBlog{99} = 'Most, 99';

$www::My_Prefs{elogsRange} = '90';
$www::Prefs_Defaults{elogsRange} = '90';
$www::Prefs_Labels{elogsRange} = 'Summary Date Range';
# these numbers are 0 relative
$www::elogsRange{0}    = 'None';
$www::elogsRange{6}    = 'Week';
$www::elogsRange{29}   = 'Month';
$www::elogsRange{90}   = 'Default, Quarter';
$www::elogsRange{111}  = 'Sixteen Weeks';
$www::elogsRange{182}  = 'Six Months';
$www::elogsRange{364}  = 'Year';
$www::elogsRange{3649} = 'Decade';

$www::HTML{Foot} = "
<div class='panel panel-footer'>
  <center>
  Select <div class='btn btn-success'>Button</div> to remove filter in <b>List View</b><br>
  Use <a href='$ENV{SCRIPT_NAME}?Prefs=Edit&Table=elogs'><i class='fa fa-user-md'></i>Settings</a> to change your preference.
  </center>
</div>\n";

# count the bytes sent
$API::bytes = 0;
$API::queries = 0;
$API::sql = '';

$API::Year{Min} = 1999;
$API::Year{Max} = (localtime)[5]+1900;

$www::undefined                = '';
$www::ByName                   = '';
@www::List_Eqmt                = ();
@www::MyUsers                  = ();
@www::Light_Colors             = ();

require "./.mmtc_passwd.pl";
require "./MMTC_API.pl";
require "./WorkoutTracker_Calendar.pl";

##$www::Menu_Labels{Main_Menu} = $www::Name{$ENV{REMOTE_USER}}.'\'s<br>Home';
$www::Menu_Labels{Main_Menu} = "My<br>Calendar";

$www::ImageTags{Map}         = '<img src=/images/google-map.jpg border=0>';
$www::ImageTags{runkeeper}   = '<img src=/images/runkeeper.jpg border=0>';
$www::ImageTags{garmin}      = '<img src=/images/garmin_logo.jpg border=0>';
$www::ImageTags{ridewithgps} = '<img src=/images/ridewithgps.png border=0>';
$www::ImageTags{strava}      = '<img src=/images/strava_logo.jpg border=0>';

$API::Site{enum_admin} = 'carlandvalerie';
$www::db_org_table = 'athletes';
$www::db_accounts_table = '';
$www::db_accounts_table = $www::db_org_table;
$www::db_ip_table = '';
$www::db_ip_table = 'athletes_by_ip';
$www::db_METs_table = 'METS';
$www::join_column = 'Workout = Activity';

$www::Size{TextAreas} = 200;
$www::Size{Notes} = 15;

$www::Subject = '';
$www::Subject = 'Leg';

# set the background color
$www::Prefs_Defaults{elogsBgColor} = '#BEDCFE';
$www::Prefs_Defaults{elogsBgColor} = 'white';
$www::Prefs_Labels{elogsBgColor} = 'Background Color';
$www::elogsBgColor{'#CF041F'} = 'Chiefs';
$www::elogsBgColor{'#C0C0C0'} = 'Patriots';
$www::elogsBgColor{'#4169E1'} = 'Cowboys';
$www::elogsBgColor{'#400459'} = 'Ravens';
$www::elogsBgColor{'#FFB6C1'} = 'Terps';
foreach my $color (@www::Light_Colors) {
  $www::elogsBgColor{$color} = $color;
}
$www::My_Prefs{elogsBgColor} = $www::Prefs_Defaults{elogsBgColor};

# the API treats all numerics in %2.2f format, override that
$www::Format{Numeric}   = '%1.0f';
$www::Format{Miles}     = '%1.3f';
$www::Format{MPH}       = '%.1f';
$www::Format{KPH}       = '%.1f';
$www::Format{DateRange} = "<div class='col-sm-4'>%.1f miles in %d days &#064; %s/%.1f MPH</div>";
$www::Format{DateRange} = "<div>%.1f miles in %d days &#064; %s/%.1f MPH</div>";
# psuedo columns cannot be listed here, yet
$www::numeric_field = 'Miles:KMs:Climb:Calories:Avg_HR:Max_HR:Temp';
##WHY$www::numeric_field = 'Miles:KMs:METS';
$www::English = 'MPH:MinMile:Sec50ydLap';
$www::Metric  = 'KPH:MinKM:Sec50mLap';
$www::average_field = "$www::numeric_field:Time:$www::English:$www::Metric:Temp";
foreach my $col (split(/:/, $www::average_field)) {
  $API::Total{$col} = 0;
  $API::Count{$col} = 0;
}

@www::Default_Display =
  ('ID',
   'Athlete',
   'Date',
   'WorkOut',
   'Leg',
   'Miles',
   'KMs',
   'Time',
   'MPH',
   'KPH',
   'MinMile',
   'MinKM',
   'Brief');

@www::LogsCols =
  ('ID',
   'Athlete',
   'Date',
   'Day',
   'Week',
   'Month',
   'Year',
   'WorkOut',
   'Leg',
   'Miles',
   'KMs',
   'Time',
   'MPH',
   'KPH',
   'MinMile',
   'MinKM',
   'Sec50ydLap',
   'Sec50mLap',
   'TTT',
   'METS',
   'Brief',
   'Notes',
   'History',
   'Modified');

@www::BlogCols =
  ('ID',
   'Athlete',
   'Date',
   'WorkOut',
   'Leg',
   'Miles',
   'KMs',
   'Time',
   'MPH',
   'KPH',
   'MinMile',
   'MinKM',
   'Brief');

@www::WeekCols =
  ('Time',
   'MPH',
   'KPH',
   'MinMile',
   'MinKM',
   'Brief');

@www::Summary_Lists =
  ('Year',
   'WorkOut',
   'Leg');

# initialize per-column Help
foreach (@www::LogsCols) {
  if (grep(/$_/, $www::numeric_field)) {
      $www::Help{$_} .= 'Enter number or simple, SQL math';
  } else {
      $www::Help{$_} = $_;
  }
}
$www::Help{Notes} = 'Multi-line free-text ascii.
First line or 80 characters with be used as the race name.';

$API::Label{Notes}          = 'Race Name/Report';
$API::Label{TTT}            = 'Days Ago';
$API::Label{METS}           = 'Metabolic<br>Equiv.';
$API::Label{METS}           = 'METs';
$API::Label{Eqmt}           = 'Equipment';
$API::Label{MinMile}        = 'Minute/Mile';
$API::Label{Sec50ydLap}     = 'Seconds/50yd lap';
$API::Label{Sec50mLap}      = 'Seconds/50m lap';

##@www::Default_GroupBy = ( 'Athlete' );

# list the name columns, the 1st one id the default filter
@www::Names =
  ('Athlete',
   'User');

# initialize all columns
foreach (@www::LogsCols,
         @www::Names,
         'Hide') {
  $www::Form{$_} = '';
}

@www::TextAreas =
    ('Notes');

# define holidays & other special days

# define the psuedo columns
$www::SQL{KMs}        = "ROUND(Miles * $My::Mile{km} * 100)/100";
$www::SQL{Day}        = 'DAYNAME(Date)';
$www::SQL{DayMonth}   = 'DAYOFMONTH(Date)';
$www::SQL{Month}      = 'CONCAT(YEAR(DATE), \'-\', MONTHNAME(Date))';
$www::SQL{MonthName}  = 'MONTHNAME(Date)';
$www::SQL{Year}       = 'YEAR(Date)';
$www::SQL{MPH}        = 'Miles / (TIME_TO_SEC(Time) / 3600)';
$www::SQL{KPH}        = "($www::SQL{KMs}) / (TIME_TO_SEC(Time) / 3600)";
$www::SQL{MinMile}    = 'SEC_TO_TIME(ROUND(TIME_TO_SEC(Time) / Miles))';
$www::SQL{MinKM}      = "SEC_TO_TIME(ROUND(TIME_TO_SEC(Time) / ($www::SQL{KMs})))";
$www::SQL{Sec50ydLap} = "TIME_TO_SEC(Time) / ROUND($My::Mile{lap_50yd} * Miles)";
$www::SQL{Sec50mLap}  = "TIME_TO_SEC(Time) / ROUND($My::Mile{lap_50m} * Miles)";
$www::SQL{SECS}       = 'TIME_TO_SEC(Time)';
$www::SQL{TTT}        = 'DATEDIFF(CURDATE(), Date)';
$www::SQL{Brief}      = 'LEFT(LEFT(Notes, -1+LOCATE("<", CONCAT(Notes, "<"))),80)';
# read from extra table
$www::SQL{METS}       = '(Miles * METS ) / Distance';
# square Miles so that division by Miles is NULL whenever Miles is 0
$www::SQL{METS}       = 'COALESCE((Miles * Miles * METS / Miles) / Distance, ((TIME_TO_SEC(Time) * METS ) / 3600))';

##$www::SQL{Age}     = 'FLOOR(DATEDIFF(CURDATE(), DOB) / 365)';

@www::Check = ();
@www::Check = ('Athlete','History');

$API::Extras = '<td><a target=WX title=SRWX href="http://mywebpages.comcast.net/srwx/htm/daily.htm">
<img src="http://banners.wunderground.com/banner/infobox_both/language/www/US/MD/Ellicott_City.gif" height=108 width=144></a></td>';

$API::AccuWeather = "<td>
<div style='width: 728px; height: 90px; background-image: url( http://vortex.accuweather.com/adcbin/netweather_v2/backgrounds/summer1_728x90_bg.jpg ); background-repeat: no-repeat; background-color: #6CB2EB;' >
<div id='NetweatherContainer' style='height: 74px;' >
<script src='http://netweather.accuweather.com/adcbin/netweather_v2/netweatherV2ex.asp?partner=netweather&tStyle=normal&logo=1&zipcode=12345&lang=eng&size=11&theme=summer1&metric=0&target=_self'>
</script>
</div>
<div style='text-align: center; font-family: arial, helvetica, verdana, sans-serif; font-size: 11px; line-height: 16px; color: #0000FF;' >
<div style='float: left; padding-left: 10px;'>
<a style='color: #0000FF' href='http://www.accuweather.com/us/md/ellicott-city/12345/city-weather-forecast.asp?partner=accuweather' >Weather Forecast</a> | <a style='color: #0000FF' href='http://www.accuweather.com/maps-satellite.asp' >Weather Maps</a> | <a style='color: #0000FF' href='http://www.accuweather.com/index-radar.asp?partner=accuweather&zipcode=12345' >Weather Radar</a> | <a style='color: #0000FF' href='http://hurricane.accuweather.com/hurricane/index.asp' >Hurricane Center</a>
</div>
<img style='float: right; padding-right: 5px;' src='http://vortex.accuweather.com/adc2004/common/images/misc/netweather_v2/adcwhite.png' />
</div>
</div>
</td>";

$www::HTML{Legal} = "<p>
<b>ACCEPTANCE OF TERMS</b><br>
Welcome to Carl's Workout Tracker.<br>
This tool was written to replace an spreadsheet that contained my exercise records.
It's not the best tool in the world, but it's free, has no pop-ups, and is usually available via the Intranet, as long as my children do not turn off my computer.
I would like to move it to a server-site, but until I get more users it will stay at home.
Anyway, I would never give away anyone's email address or use the address for any non-system related messages.
Currently, the password is stored in using 64-bit DES encryption, but is sent 'in the clear' to the server.
Of course I will try to keep the system and your data available, but if my hard disk dies, we will lose the data.
I backup to a network drive every so often, but the PC is pretty new and I've been lulled into a false sense of security.
<p>
";


#*****************************************************************************
#
# Routine:  read_blog
#
#*****************************************************************************
sub read_blog {
  my $here = $API::program.'::read_blog';

  my $lines = shift(@_);
  my $usr = $ENV{REMOTE_USER};
  if ($API::diag > 2) {
    print STDERR "$here: lines=$lines usr=$usr\n";
  }

  if ($www::My_Prefs{elogsBlog} < 0) {
     $www::HTML{Foot} = "
<div class='panel panel-footer'>
<a href='http://www.strava.com/clubs/4094/latest-rides/202b667a0412a22bf0777dc852157724c12ed79a' width='300'>MMTC Strava Club
</a>
</div>\n";
     $www::HTML{Foot} = "
<div class='panel panel-footer'>
<iframe height='454' width='300' frameborder='0' allowtransparency='true' scrolling='no' src='https://www.strava.com/clubs/Mid-MarylandTriClub/latest-rides/202b667a0412a22bf0777dc852157724c12ed79a?show_rides=true'></iframe>
<iframe height='160' width='300' frameborder='0' allowtransparency='true' scrolling='no' target=_blank src='https://www.strava.com/clubs/Mid-MarylandTriClub/latest-rides/202b667a0412a22bf0777dc852157724c12ed79a?show_rides=false'></iframe>
</div>\n";
     return;
  }

  my $sth = $API::dbh->prepare("SELECT MAX(ID) AS MAX
FROM $www::db_logs_table;");
  $API::queries++;
  $sth->execute();

  my $ref = $sth->fetchrow_hashref();
  my $max = $ref->{MAX} - $lines + 1;

  $API::sql = "SELECT *\n";
  # select the pseudo columns
  foreach my $key (keys %www::SQL) {
    unless ($key =~ /METS/) {
      $API::sql .= ", $www::SQL{$key} AS $key\n";
    }
  }
  $API::sql .= "FROM $www::db_logs_table
WHERE ID >= ?
OR TO_DAYS(NOW()) - TO_DAYS(Date) = 0
ORDER BY ID DESC;";

  $sth = $API::dbh->prepare($API::sql);
  $API::queries++;
  $sth->execute($max);

  my $th = '';
  $th = '<tr>';
  foreach my $col (@www::BlogCols) {
    # only add columns that the user choose (English/Metric)
     unless (grep(/$col/, @www::Default_Display)) {
       next;
     }
     if ($col eq 'ID') {
       $th .= "<th width=1%>$col</th>";
     } else {
       $th .= "<th>$col</th>";
     }
  }
  $th .= "</tr>\n";

  my $td = '';
  while (my $ref = $sth->fetchrow_hashref()) {
    $td .= "<tr>\n";
    foreach my $col (@www::BlogCols) {
      # only add columns that the user choose (English/Metric)
      unless (grep(/$col/, @www::Default_Display)) {
        next;
      }
      my $value = '';
      my $cell = $ref->{$col};
      if ($cell) {
        if ($col eq 'ID') {
          $value = &get_id_link($API::subtitle, $cell);
        } elsif ($col eq 'Date') {
          $value = &get_column($API::subtitle, $col, $cell, 'ID', '');
        } elsif ($col eq 'Athlete') {
          ##$value = &get_column($API::subtitle, $col, $cell, 'ID', 'Athlete');
          ##$value =~ s/$cell</$www::Name{$cell}</g;
          if ($www::Name{$cell}) {
            my $href = "$ENV{SCRIPT_NAME}?Edit=Calendar";
            my $userid = $www::Name{$cell};
            my $title = "View Calendar for $userid";
            my $year = $API::Date{year};
            my $month = $API::Date{month};
            $value = "<a title='$title' href=\"$href&Athlete=$cell&Year=$year&Month=$month&\">$userid</a>\n";
          } else {
            $value = $cell;
          }
        } else {
          $value = $cell;
        }
      }
      $td .= "<td valign=top><nobr><small>$value</td>";
    }
    $td .= "</tr>\n";
  }
  $www::HTML{Foot} .= "
<div class='panel panel-footer'>
  <table class='table table-striped'>
  <caption><h3>All Training Activity Blog</h3></caption>
  $th
  $td
  </table>
</div>\n";

}


#*****************************************************************************
#
# Routine:  add_subfilters
#
#*****************************************************************************
sub add_subfilters {
  my $here = $API::program.'::add_subfilters';

  my $field = shift(@_);
  my $enum = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: field=$field, enum=$enum\n";
  }

  my $sub = '';

  if ($field eq 'Date') {
    my $title = "List All $API::subtitle $www::OnAfter{title} $enum";
    my $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=GrEq$field&GrEq$field=$enum&OrderBy=$field\"";
    $sub = "<$a>$www::OnAfter{tag}</a>";

    $title = "List All $API::subtitle $www::BeforeOn{title} $enum";
    $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=LtEq$field&LtEq$field=$enum&OrderBy=$field\"";
    $sub .= "<$a>$www::BeforeOn{tag}</a>";

  } elsif ($field eq 'Temp') {
    my $title = "List All $API::subtitle $www::GrEq{title} $enum";
    my $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=GrEq$field&GrEq$field=$enum&OrderBy=$field\"";
    $sub = "<$a>$www::GrEq{tag}</a>";

    $title = "List All $API::subtitle $www::LtEq{title} $enum";
    $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=LtEq$field&LtEq$field=$enum&OrderBy=$field\"";
    $sub .= "<$a>$www::LtEq{tag}</a>";

   } else {
      my $title = "List All $API::subtitle $www::GrEq{title} $enum";
      my $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=GrEq$field&GrEq$field=$enum&OrderBy=$field\"";
      $sub = "<$a>$www::GrEq{tag}</a>";
   }

   return $sub;
}


#*****************************************************************************
#
# Routine:  browse_by_count
#
#*****************************************************************************
sub browse_by_count {
  my $here = $API::program.'::browse_by_count';

  my $usr = $ENV{REMOTE_USER};
  if ($API::diag > 2) {
    print STDERR "$here: usr=$usr\n";
  }

  my $msg = 'Browse All';
  &header($msg, '');

  my $out = "<table class='table'>\n<tr>";
  no strict 'refs';

  my $max = $#www::List_Eqmt + 1;

  # add the normal tables
  @www::List_Athlete = @www::MyUsers;
  foreach my $field ('Athlete', @www::Enum_Lists) {
    my $list_table;
    &read_count('logs', $field);

    # change underscores to spaces
    (my $label = $field) =~ s/_/ /g;

    $list_table .= "<td valign=top>
<table class='table table-hover'>
<tr><th><nobr>$label</nobr><br>List</th></tr>\n";

    my $array = "www::List_$field";
    my $disarray = "www::Disabled_$field";
    my $i = 0;
    foreach my $enum (@$array) {
      if ($www::logs_count{$enum}) {
        if ($i++ >= $max) {
          last;
        }
      }
      my $a;
      my $greqa = '';
      my $title = "List All $enum $API::subtitle";

      $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=$field&$field=$enum&OrderBy=$www::Default{OrderBy}\"";
      if (($field eq 'Date') ||
          ($field eq 'Temp') ||
          ($www::numeric_field =~ /$field/)) {
        # add sub-filters to numeric fields
        $greqa = &add_subfilters($field, $enum);
      }

      if ($www::logs_count{$enum}) {
        $list_table .= "<tr bgcolor=white><td align=left><nobr>
<$a><font class=font_$enum>$enum</font></a> ($www::logs_count{$enum}) $greqa
</nobr></td>
</tr>\n";
      }
    }
    $list_table .= "</table>\n\n</td>\n";
    # add the new table to the output buffer
    $out .= $list_table;
  }

  $out .= "
</tr>
</table>\n\n";

  print $out;
  $API::bytes += length($out);

  &footer('');
}


#*****************************************************************************
#
# Routine:  view_carousel
#
#*****************************************************************************
sub view_carousel {
  my $here = $API::program.'::view_carousel';

  my $usr = $ENV{REMOTE_USER};
  if ($API::diag > 2) {
    print STDERR "$here: usr=$usr\n";
  }

  ##&header('', "Images from $www::My_Prefs{elogsImages}");
  &header('', '');

  my $max = $www::My_Prefs{elogsCI};
  my $out = "
<div class='container'>
  <div id='myCarousel' class='carousel slide' data-ride='carousel' data-interval='1000000'>
    <!-- Indicators -->
    <ol class='carousel-indicators'>";
  foreach my $img (1..$max) {
     my $class = '';
     if ($img == 1) {
        $class = "class='active'";
     }
     $out .= "<li data-target='#myCarousel' data-slide-to='$img' $class></li>";
  }
  $out .= "
    </ol>

    <!-- Wrapper for slides -->
    <div class='carousel-inner' role='listbox'>";
  
  foreach my $img (1..$max) {
     &select_image($www::My_Prefs{elogsImages});
     my $image = $www::HTML{Image};
     my $active = '';
     if ($img == 1) {
        $active = 'active';
     }
     my $onmouseover = "";
     if ($www::My_Prefs{elogsIH} eq 'F') {
        $onmouseover = "onmouseover=\"showhint('<center><img src=$image border=0></center>', this, event, '100px')\" ";
     }
     $out .= "
      <div id='div$img' class='item $active'>
        <a $onmouseover href=$image><img src=$image border=0 height=$www::HTML{ImageHeight}></a> 
      </div>";
  }
  $out .= "
    </div>

    <!-- Left and right controls -->
    <a class='left carousel-control' href='#myCarousel' role='button' data-slide='prev'>
      <span class='glyphicon glyphicon-chevron-left' aria-hidden='true'></span>
      <span class='sr-only'>Previous</span>
    </a>
    <a class='right carousel-control' href='#myCarousel' role='button' data-slide='next'>
      <span class='glyphicon glyphicon-chevron-right' aria-hidden='true'></span>
      <span class='sr-only'>Next</span>
    </a>
  </div>
</div>";

  print $out;
  $API::bytes += length($out);

  &footer('');
}



#*****************************************************************************
#
# Routine:  splash
#
#*****************************************************************************
sub splash {
   my $here = $API::program.'::splash';

   my $usr = $ENV{REMOTE_USER};
   if ($API::diag > 2) {
      print STDERR "$here: usr=$usr\n";
   }

   $www::My_Prefs{elogsHome} = 'U';

   if ($ENV{REMOTE_USER} ne $API::Site{guest}) {
      if ($www::My_Prefs{elogsHome} eq 'C') {
         $www::HTML{Foot} = '';
         &read_blog($www::My_Prefs{elogsBlog});
         &view_carousel();

      } elsif ($www::My_Prefs{elogsHome} eq 'U') {
         my $href='$ENV{SCRIPT_NAME}?$www::Custom{Upcoming_Races}&GrEqDate=$API::today';
         $www::Form{ListBy} = 'GrEqDate:Leg:Athlete';
         $www::Form{OrderBy} = 'Date';
         $www::Form{Show} = 'TTT';
         $www::Form{Hide} = 'Leg,Time,MPH,MinMile,Temp';
         $www::Form{Leg} = 'Race';
         $www::Form{Athlete} = '-__DELETED__';
         $www::Form{GrEqDate} = $API::today;
         &logs('List', $www::Form{ListBy}, $www::Form{OrderBy});

      } elsif ($www::My_Prefs{elogsHome} eq 'M') {
         &read_blog($www::My_Prefs{elogsBlog});
         my $range = $API::Date{month} - 1;
         if (($API::Date{day} < 8) && ($range)) {
            # unless January, use last month/this month range for first week
            $range .= ':'.$API::Date{month};
            &view_year_calendar($ENV{REMOTE_USER}, $API::Date{year}, $range);
         } else {
            &read_blog($www::My_Prefs{elogsBlog});
            &view_calendar($ENV{REMOTE_USER}, $API::Date{year}, $API::Date{month}, '');
         }

      } else {
         $www::Form{Athlete} = $ENV{REMOTE_USER};
         $www::Form{Week} = $API::Date{year}.'-'.$API::Date{week};
         &logs('View', 'Athlete:Week', $www::Default{WeekOrder});
      }

   } else {
      &login($ENV{REMOTE_ADDR}, '');
      ##&browse_by_count;
   }
}


#*****************************************************************************
#
# Routine:  get_week_buttons
#
#*****************************************************************************
sub get_week_buttons {
  my $here = $API::program.'::get_week_buttons';

  my $bye = shift(@_);
  my $week = shift(@_);
  my $order = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: bye=$bye week=$week order=$order\n";
  }

  my ($year, $wk) = split(/-/, $week);
  my $next_yr = $year;
  my $next = $wk + 1;
  if ($next == 53) {
      $next_yr++;
      $next = 0;
  }
  my $next_lbl = "$next_yr-$next";

  my $back_yr = $year;
  my $back = $wk - 1;
  if ($back == -1) {
      $back = 52;
      $back_yr--;
  }
  my $back_lbl = "$back_yr-$back";

  # add Athlete info if specified
  my $field = 'Week';
  my $athlete = '';
  if ($www::Form{Athlete}) {
      $athlete = "&Athlete=$www::Form{Athlete}";
      $field = 'Athlete:Week';
  }

  my $caption = &get_calendar_caption($www::Form{Athlete}, $year, 11);

  my $week_lbl = $API::Date{year}.'-'.$API::Date{week};
  my $href = "$ENV{SCRIPT_NAME}?Athlete=$www::Form{Athlete}";
  my $out .= "</tr>
</table>
<div class='btn-group' role='group'>
<a title='$back_lbl'      href=\"$href&$bye=$field&OrderBy=$order&Week=$back_lbl\" class='btn btn-default' role='button'><i class='fa fa-arrow-left'></i></a>
<a title='View This Week' href=\"$href&$bye=$field&OrderBy=$order&Week=$week_lbl\" class='btn btn-default' role='button'><i class='fa fa-undo'></i></a>
<a title='$next_lbl'      href=\"$href&$bye=$field&OrderBy=$order&Week=$next_lbl\" class='btn btn-default' role='button'><i class='fa fa-arrow-right'></i></a>
</div>
<br>
<table class='table table-striped'>
<tr>\n";

  return $out;
}


#*****************************************************************************
#
# Routine:      read_count_filtered
#
#*****************************************************************************
sub read_count_filtered {
  my $here = $API::program.'::read_count_filtered';

  my $type = shift(@_);
  my $group = shift(@_);
  my $field = shift(@_);
  my $where = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: type=$type group=$group field=$field where=$where\n";
  }

  no strict 'refs';
  my $array = "www::List_$field";

  my $count = "www::${type}_count";
  my $sum = "www::${type}_sum";
  my $table = "www::db_${type}_table";
  foreach my $enum (@$array) {
    if ($enum) {
      $$count{$enum} = 0;
    }
  }

  my $calc_sql = $field;
  my $select = $field;
  if ($www::SQL{$field}) {
      $calc_sql = $www::SQL{$field};
      $select = "$calc_sql AS $field";
  }

  my $sql = "SELECT COUNT($calc_sql) AS Count, SUM($group) AS Sum, $select
FROM $$table
WHERE $where
GROUP BY $field;";

  if ($API::diag > 3) {
    print STDERR "$here: sql=$sql\n";
  }
  # build the list
  my $sth = $API::dbh->prepare($sql);
  $API::queries++;
  $sth->execute();

  while (my $ref = $sth->fetchrow_hashref()) {
    my $value = $ref->{$field};
    $$count{$value} = $ref->{Count};
    $$sum{$value} = $ref->{Sum};
    if ($API::diag > 4) {
      print STDERR "$here: count=$count field=$field value=$value Count=$ref->{Count}\n";
    }
  }
}


#*****************************************************************************
#
# Routine:  summary
#
#*****************************************************************************
sub summary {
  my $here = $API::program.'::summary';

  my $usr = $ENV{REMOTE_USER};
  my $group = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: usr=$usr group=$group\n";
  }

  my $msg = "$www::Name{$usr} Summary By $group";
  &header($msg, '');

  my $out = "<table class='table'>\n<tr>";
  no strict 'refs';

  # add the normal tables
  @www::List_Athlete = @www::MyUsers;
  foreach my $field (@www::Summary_Lists) {
    my $list_table;
    my $where = "Athlete = '$usr' AND Date <= NOW()";
    &read_count_filtered('logs', $group, $field, $where);

    # change underscores to spaces
    (my $label = $field) =~ s/_/ /g;

    $list_table .= "<td valign=top>
<table class='table table-hover'>
<tr><th><nobr>$label</nobr> List</th></tr>\n";

    my $array = "www::List_$field";
    my $disarray = "www::Disabled_$field";
    my $i = 0;
    foreach my $enum (@$array) {
      my $title = "List All $enum $API::subtitle";
      my $a = "a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=Athlete:$field&Athlete=$usr&$field=$enum&OrderBy=$www::Default{OrderBy}\"";
      my $value = '';
      if ($www::logs_sum{$enum}) {
         $value = sprintf($www::Format{MPH}, $www::logs_sum{$enum});
      }
      if ($www::logs_count{$enum}) {
        $list_table .= "<tr bgcolor=white>
<td align=left><nobr>
<$a><font class=font_$enum>$enum</font></a>,
$value $group
($www::logs_count{$enum} $API::subtitle)
</nobr></td>
</tr>\n";
       }
     }
     $list_table .= "</table>\n\n</td>\n";
     # add the new table to the output buffer
     $out .= $list_table;
  }

  $out .= "
</tr>
</table>\n\n";

  print $out;
  $API::bytes += length($out);

  &read_blog($www::My_Prefs{elogsBlog});
  &footer('');
}


#*****************************************************************************
#
# Routine:  build_buttons
#
#*****************************************************************************
sub build_buttons {
  my $here = $API::program.'::build_buttons';

  if ($API::diag > 2) {
    print STDERR "$here:\n";
  }

  if ($www::My_Prefs{elogsHome} eq 'W') {
    my $this_week = $API::Date{year}.'-'.$API::Date{week};
    $www::Menu_Labels{Main_Menu} = "My<br>Calendar<br>$this_week";
  }

  my $mmm = $www::Months[$API::Date{month} - 1];
  my $wk = $API::Date{week};
  my $value = "option class=child value=\"?OrderBy=$www::Default{OrderBy}&ListBy";
  my $valu2 = "option class=child value=\"?OrderBy=$www::Default{WeekOrder}&ViewBy";
  my $calMonth = "option class=child value=\"?Edit=Calendar";
  my $calYear = "option class=child value=\"?Edit=CalYear";

  my $mgmt = '';
  if (($ENV{REMOTE_USER} eq $API::Site{enum_admin}) &&
      ($www::My_Prefs{elogsMgmt} eq 'A')) {
    $mgmt = "<td align=left>
  <a class='mainMenu' href=\"$ENV{SCRIPT_NAME}?Edit=WorkOut\"><font color=red>WorkOut<br>Mgmt</a>
</td>
<td align=left>
  <a class='mainMenu' href=\"$ENV{SCRIPT_NAME}?Edit=Leg\"><font color=red>Leg<br>Mgmt</a>
</td>
<td align=left>
  <a class=mainMenu href=\"$ENV{SCRIPT_NAME}?Edit=Printenv\"><font color=red>Print<br>Env</a>
</td>
";
  }

  my $week = $API::Date{year}.'-'.$API::Date{week};
  $API::today = &get_date('Today');
  $API::navbar = "";
  $API::navbar = "
<li class=''><a title='My Training Calendar' 
       href='$ENV{SCRIPT_NAME}?Edit=Calendar&Athlete=$ENV{REMOTE_USER}'><i class='fa fa-calendar'></i> Calendar</a></li>
<li class='hidden-md'><a title='Upcoming Races' 
       href='$ENV{SCRIPT_NAME}?$www::Custom{Upcoming_Races}&GrEqDate=$API::today'><i class='fa fa-tachometer'></i> Upcoming</a></li>
<li class='hidden-md'><a title='Carousel $www::My_Prefs{elogsCI} images from $www::My_Prefs{elogsImages} at $www::My_Prefs{elogsCH}'
       href='$ENV{SCRIPT_NAME}?Edit=Carousel&Athlete=$ENV{REMOTE_USER}'><i class='fa fa-picture-o'></i> Carousel</a></li>
<li class='hidden-md'><a title='My Training Summary' 
       href='$ENV{SCRIPT_NAME}?Edit=Summary&Summary=Miles'><i class='fa fa-user'></i> Summary</a></li>
<li class='hidden-md'><a title='Explore All Entries' 
       href='$ENV{SCRIPT_NAME}?Edit=BrowseByCount'><i class='fa fa-users'></i> Explore</a></li>
<li class=''><a title='Search Entries' 
       href='$ENV{SCRIPT_NAME}?Edit=Report&Table=$www::db_logs_table,$www::db_METs_table'><i class='fa fa-search'></i> Search</a></li>";

  $API::burger = "";
  $API::burger = "
<span class='icon-bar' name='one'   id='one'></span>
<span class='icon-bar' name='two'   id='two'></span>
<span class='icon-bar' name='three' id='three'></span>
<span class='icon-bar' name='four'  id='four'></span>
";

}


#*****************************************************************************
#
# Routine:      get_small_id
#
#*****************************************************************************
sub get_small_id {
   my $here = $API::program.'::get_id';

   my $ref = shift(@_);
   my $id = shift(@_);
   my $msg = shift(@_);
   my $order = shift(@_);
   if ($API::diag > 2) {
      print STDERR "$here: id=$id msg=$msg order=$order\n";
   }

   # use users preference on how to open this window
   my $title;
   my $target;
   if (($www::My_Prefs{APIOutWin}) &&
       ($www::My_Prefs{APIOutWin} eq 'Blank')) {
      $title = "View $msg $id in a new window";
      $target = 'target=_blank';
   } else {
      $title = "View $msg $id in this window";
      $target = 'target=_self';
   }

   my $td = "<a title=\"$title\" $target href=\"$ENV{SCRIPT_NAME}?ViewBy=ID&OrderBy=$order&ID=$id&IDL=$www::null_IDL\">$id</a>";

   unless ($www::ID_List eq $www::null_IDL) {
      $www::ID_List .= $id.':';
      if (length($www::ID_List) > 1024*2) {
         $www::ID_List = $www::null_IDL;
      }
   }
   return $td;
}


#*****************************************************************************
#
# Routine:  view_logs_list
#
#*****************************************************************************
sub view_logs_list {
  my $here = $API::program.'::view_logs_list';

  my $only = shift(@_);
  my $order = shift(@_);
  my $sth = shift(@_);
  my $field = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: only=$only, order=$order sth=$sth field=$field\n";
  }

  my $fonly = &get_full_name($only);
  my $out = "\n<div class='scrollableContainer'>
<div class='panel panel-body'>
<table class='table table-striped'>
<thead>\n";

  # hide is not always/usually specified and is different from sort
  my $hide = $www::Form{Hide};
  my $andHide = '';
  $hide =~ s/^,//g;
  if ($hide) {
    if ($API::diag > 3) {
      print STDERR "$here: hiding $hide\n";
    }
    $andHide = "&Hide=$hide";
  }

  # show is not always/usually specified and is different from sort
  my $show = '';
  my $andShow = '';
  if ($www::Form{Show}) {
     $show = $www::Form{Show};
     $show =~ s/^,//g;
     if ($show) {
	if ($API::diag > 3) {
	   print STDERR "$here: showing $show\n";
	}
	$andShow = "&Show=$show";
     }
  }

  $out .= '<tr>';
  foreach my $col (@www::Default_Display, $show) {
    my $colOrderBy = $col;
    if (($col eq 'ID') &&
        $www::My_Prefs{APIOutDir}) {
       $colOrderBy .= ' '.$www::My_Prefs{APIOutDir};
    }

    if (($col ne 'ID') &&
       (($field =~ /^$col/) ||
        ($field =~ /:$col/)) &&
        ($only =~ /^[^,]*$/)) {
      # do not print the selected field, its in the header
      # change from eq to expression to support multiple fields
      # only skip when they are unique, not called from build_report
      ##print STDERR "$here: skipping field=$field col=$col\n";
      if ($col eq 'Eqmt') {
        ##&& ($cell =~ /^\-/)) {
        print STDERR "$here: NOT skipping field=$field col=$col\n";
      } else {
        next;
      }
    }

    # hide user selected columns
    if (grep(/$col/, $hide)) {
      next;
    }
    my $hr;
    # change underscores to word-breaks
    my $label = $col;
    if ($API::Label{$col}) {
      $label = $API::Label{$col};
    }
    $label =~ s/_/ <wbr>/g;
    $API::Total{$col} = 0;
    $API::Count{$col} = 0;

    my $subSort = '';
    unless ($only =~ /,/) {
      my $title = "List $fonly Sorted by $colOrderBy";
      if ($hide) {
        $title .= " without $hide";
      }
      unless (($order =~ /ID/) ||
              ($order =~ /$col/)) {
        $subSort = "List $fonly Sorted by $order,$colOrderBy";
        if ($hide) {
          $subSort .= " without $hide";
        }
      }
      my $fields;
      unless ($field =~ (/:/)) {
        $fields = "&$field=$only";
      } else {
        foreach (split(/:/, $field)) {
          $fields .= "&$_=$www::Form{$_}";
        }
      }

      # build the hide button
      unless ($col eq 'ID') {
        my $subHide;
        $subHide = "List $fonly Sorted by $order without $hide,$col";
        $subHide =~ s/without ,/without /g;
        $hr .= "<a title=\'$subHide\' href=\"$ENV{SCRIPT_NAME}?ListBy=$field&OrderBy=$order&Hide=$hide,$col$fields\">$www::ImageTags{Hide}</a>";
        $hr =~ s/=,/=/g;
      }

      $hr .= "<a title=\'$title\' href=\"$ENV{SCRIPT_NAME}?ListBy=$field$andHide$andShow&OrderBy=$colOrderBy$fields\">$label</a>";
      if ($subSort) {
        $hr .= "<a title=\'$subSort\' href=\"$ENV{SCRIPT_NAME}?ListBy=$field$andHide$andShow&OrderBy=$order,$colOrderBy$fields\">$www::ImageTags{Sort}</a>";
      }
    } else {
      $hr = "<b>$label</b>";
    }
    $out .= "<th class='$col'><nobr><div>$hr</div></th>\n";
  }
  $out .= "</tr>\n";
  $out .= "</thead>
<tbody>\n";

  my $items = 0;
  while (my $ref = $sth->fetchrow_hashref()) {
    $items++;
    $out .= '<'.&get_tr_start.'>';
    foreach my $col (@www::Default_Display, $show) {

      my $cell = '';
      if ($ref->{$col}) {
        $cell = $ref->{$col};
        # add up the grand total
        if ($www::average_field =~ /$col/) {
          $API::Count{$col}++;
          if ((($col eq 'Time') ||
               ($col eq 'MinKM') ||
               ($col eq 'MinMile')) &&
              ($cell =~ /([0-9:\.]+)/)) {
            my ($hh, $mm, $ss) = split(/:/, $1);
            ##print STDERR "$here: hh=$hh, mm=$mm, ss=$ss\n";
            my $seconds = $ss + ($mm * 60) + ($hh * 3600);
            $API::Total{$col} += $seconds;
            ##print STDERR "$here: API::Total{$col}=$API::Total{$col}\n";
          } else {
            # psuedo columns, like MinMile can be NULL
            if ($cell) {
              $API::Total{$col} += $cell;
            }
          }
        }
      }

      my $td = '';
      # hide user selected columns
      if (grep(/$col/, $www::Form{Hide})) {
        next;

      } elsif ($col eq 'ID') {
        ## $td = &get_id($ref, $cell, $API::title, $order);
        $td = &get_small_id($ref, $cell, $API::title, $order);

      } elsif ((($field =~ /^$col/) ||
                ($field =~ /:$col/)) &&
               ($only =~ /^[^,]*$/)) {
        # do not print the selected field, its in the header
        # change from eq to expression to support multiple fields
        # only skip when they are unique, not called from build_report
        ##print STDERR "$here: skipping field=$field col=$col\n";
        if ($col eq 'Eqmt') {
          ##&& ($cell =~ /^\-/)) {
          print STDERR "$here: NOT skipping field=$field col=$col\n";
          $td = &get_column($API::subtitle, $col, $cell, $order, $field);
        } else {
          next;
        }

      } elsif ($col eq 'Athlete') {
        ##$td = &get_user_select_by_field($API::subtitle, $cell, $col);
        $td = &get_column($API::subtitle, $col, $cell, $order, $field);
        # display full name if available
        if ($www::Name{$cell}) {
          $td =~ s/$cell</$www::Name{$cell}</g;
        }
        ##$td = &get_user_mailto($cell);

      } elsif ($col eq 'Notes') {
        $td = $cell;

      } elsif ($col eq 'Brief') {
        my $title = &fix_alt_text($ref->{ID}, $ref->{Notes});
        # some of Johns rowing have < in them
        my $text = $ref->{Notes};
        if ($text) {
          # only popup the original text
          my $append;
          ($title, $append) = split(/<li/, $text, 2);
          $title =~ s/<br>//g;
          $title =~ s/\"//g;
          chomp($title);
        }
        $td = "<a title='$title'>$cell</a>";

      } elsif ($col eq 'Date') {
        ##$td = &get_date_column($API::subtitle, $col, $cell, $order, $field);
        $td = &get_column($API::subtitle, $col, $cell, $order, $field);

      } elsif ($col eq 'Temp') {
        if ($cell) {
          ##$td = &get_column($API::subtitle, $col, $cell, $order, $field);
          ##$td .= &add_subfilters($col, $cell);
          $td = &get_numeric_column($API::subtitle, $col, $cell, $order, $field);
        }

      } elsif (grep(/$col/, $www::numeric_field)) {
        # don't use sprintf, rounding changes selection
        #$td = &get_column($API::subtitle, $col, $cell, $order, $field);
        my $value = '';
        if ($cell) {
          $value = sprintf($www::Format{Numeric}, $cell);
        }
        if ($www::Format{$col}) {
          $value = sprintf($www::Format{$col}, $cell);
        }
        $td = &get_numeric_column($API::subtitle, $col, $value, $order, $field);

      } elsif (($col eq 'MPH') ||
               ($col eq 'KPH')) {
        my $value = 0;
        if ($cell) {
          $value = sprintf($www::Format{$col}, $cell);
        }
        $td = &get_column($API::subtitle, $col, $value, $order, $field);

      } else {
        $td = &get_column($API::subtitle, $col, $cell, $order, $field);
      }
      $out .= "<td valign=top class='$col'><nobr><div>$td</div></td>\n";
    }
    $out .= "</tr>\n";
  }
  $sth->finish();

  # add the Total and Average rows
  my $total_tr = '';
  my $average_tr = '';
  foreach my $col (@www::Default_Display, $show) {
    # hide user selected columns
    if (grep(/$col/, $www::Form{Hide})) {
      next;

    } elsif ($col eq 'ID') {

      # add number of entries
      my $count = $API::Count{Miles};
      $total_tr .= "<th>$count Entries Total</th>";
      $average_tr .= "<th>Average</th>";

    } elsif ((($field =~ /^$col/) ||
              ($field =~ /:$col/)) &&
             ($only =~ /^[^,]*$/)) {
      # do not print the selected field, its in the header
      # change from eq to expression to support multiple fields
      # only skip when they are unique, not called from build_report
      ##print STDERR "$here: skipping field=$field col=$col\n";
      next;

    } elsif ($www::average_field =~ /$col/) {
      my $days = 0;
      my $tot = '';
      my $ave = '';
      if ($API::Total{$col}) {
        if (($col eq 'Time') ||
            ($col =~ /^Min/)) {
          # convert seconds into readable time
          my $strftime_fmt = '%T';
          $days = floor($API::Total{$col} / 60 /60 / 24);
          $tot = strftime($strftime_fmt, $API::Total{$col},
                          0, 0, 0, 0, 0);
          $ave = strftime($strftime_fmt, $API::Total{$col}/$API::Count{$col},
                          0, 0, 0, 0, 0);
        } elsif ($col eq 'MPH') {
          $tot = sprintf($www::Format{Numeric},
                         $API::Total{Miles} / ($API::Total{Time} / 3600));
          my $ave_miles = $API::Total{Miles} / $API::Count{Miles};
          my $ave_time = $API::Total{Time} / $API::Count{Time};
          $ave = sprintf($www::Format{Numeric},
             $ave_miles / ($ave_time / 3600));
        } else {
          $tot = sprintf($www::Format{Numeric}, $API::Total{$col});
          $ave = sprintf($www::Format{Numeric}, $API::Total{$col} / $API::Count{$col});
        }
      }
      if($days == 1) {
        $tot = "$days day, $tot";
      } elsif($days) {
        $tot = "$days days, $tot";
      }
      my $title = sprintf('%.2f minutes', $API::Total{$col}/60);
      if($API::Total{$col} > 60*60*24*3) {
        $title = sprintf('%.2f days=%.2f hours', $API::Total{$col}/60/60/24, $API::Total{$col}/60/60);
      } elsif($API::Total{$col} > 60*60) {
        $title = sprintf('%.2f hours', $API::Total{$col}/60/60);
      }
      $total_tr .= "<td title='$title'>$tot</td>";
      $average_tr .= "<td>$ave $col</td>";

    } else {
      $total_tr .= '<td></td>';
      $average_tr .= '<td></td>';
    }
  }
  $out .= "\n</tbody>\n</table>\n</div>\n</div>\n";
  if (1) {
    $out .= "
<table class='table table-striped'>
<tr>$total_tr</tr>
<tr>$average_tr</tr>
</table>
\n";
  }

  my $extras = '';
  if ($items) {
    unless ($field eq 'All') {
      my $fields;
      my $these;
      foreach (split(/:/, $field)) {
        $fields .= "&$_=$www::Form{$_}";
        $these .= $www::Form{$_}.' ';
      }
      # if called from build_report, then Show Details does not work
      unless ($fields =~ /:/) {
        if ($order =~ /$www::Default{WeekOrder}/) {
          $www::Menu_Labels{Show_Details} = "<i class='fa fa-calendar-o'></i>";
        } else {
          $www::Menu_Labels{Show_Details} = "<i class='fa fa-columns'></i> Tile";
        }
        
        my $title = "Tile All These $these$API::title";
        $extras = "<div class='btn-group role='group'>
<a title='$title' href='$ENV{SCRIPT_NAME}?ViewBy=$field&OrderBy=$order$fields' class='$BS::other' role='button'>$www::Menu_Labels{Show_Details}</a>
</div>";
      }
    }
  } else {
    $out = "<center><h2>No <font color=red>$fonly</font> $API::subtitle Items found</h2></center>\n";
  }

  my $sorted = "By $order";
  foreach my $name (@www::Names) {
    if ($order eq $name) {
      $sorted .= ' (Athlete)';
    }
  }

  # Add extra links to next & previous
  if ($www::Form{Week}) {
    $extras .= &get_week_buttons('ListBy',
                                 $www::Form{Week},
                                 $www::Default{WeekOrder});
  }

  # change $fonly into separate URLs to allow them to be droppped
  my $fonly_URLs = '';
  if ($field =~ /:/) {
    foreach my $each (split(/:/, $field)) {
      my $fields = '';
      my $listBy = '';
      foreach my $o (split(/:/, $field)) {
        if ($each ne $o) {
          $fields .= "&$o=$www::Form{$o}";
          $listBy .= $o.':';
        }
      }
      $listBy =~ s/:$//g;
      my $nlabel = &get_full_name($www::Form{$each});
      if (($nlabel ne '') &&
          ($nlabel ne '-__DELETED__')) {
        # Do NOT create buttons unless the name is found
	$fonly_URLs .= "<input type=button class='btn btn-success' value='$nlabel' onClick=\"window.location='?ListBy=$listBy$andHide$andShow&OrderBy=$order$fields'\"> ";
	$sorted = "<input type=button class='btn btn-default' value='By $order' onClick=\"window.location='?ListBy=$listBy&OrderBy=$www::Default{OrderBy}$fields'\"> ";
      }

    }
  } else {
    $fonly_URLs = $fonly;
  }
  &header("$fonly_URLs $sorted", $extras);
  if ($API::diag > 5) {
    print STDERR "$here: www::null_IDL=$www::null_IDL www::ID_List=$www::ID_List\n";
  }
  $out =~ s/IDL=$www::null_IDL/IDL=$www::ID_List/g;
  print $out;
  $API::bytes += length($out);
  &footer('');
}


#*****************************************************************************
#
# Routine:  get_month_from_string
#
#*****************************************************************************
sub get_month_from_string {
  my $here = $API::program.'::get_month_from_string';

  my $month = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: month=$month\n";
  }

  foreach my $mmm (0..11) {
    if ($month eq $www::Months[$mmm]) {
      return $mmm + 1;
    }
  }
  return 0;
}


#*****************************************************************************
#
# Routine:      get_url_td
#
#*****************************************************************************
sub get_url_td {
  my $here = $API::program.'::get_url_td';

  my $text = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: text=$text\n";
  }

  my $td = '';
  my $target = 'target=_blank';
  my $title;
  foreach my $line (split(/\n/, $text)) {
    if ($line =~ /^(.*)(https?:\/\/[^<]+)(.*)$/) {
      my $pre = $1;
      my $href = $2;
      my $post = $3;
      # revert double-converted ampersands back
      $href =~ s/&ampamp/&/g;
      # revert single-converted ampersands back also
      $href =~ s/&amp;/&/g;
      $href =~ s/&amp/&/g;
      # don't display the entire URL if it is very long
      my $url = $href;
      $url =~ s/,/,<wbr>/g;
      $title = 'View URL in a new window';
      if (($href =~ /\/map/) ||
          ($href =~ /\.map/)) {
        $title = "View map in a new window";
        if ($href =~ /https?:\/\/([^\/]+)\//) {
          $title = "View map from $1 in a new window";
        }
        $td .= "$pre<a title='$title' $target href='$href'>$www::ImageTags{Map} Map from $1</a>$post<br>";
        if ($href =~ /google/) {
          $td .= "<iframe width=425 height=350 frameborder=0 scrolling=no src=\"$href&output=embed\"></iframe>";
        }

      } elsif (($href =~ /\W(runkeeper)\.com\//) ||
	       ($href =~ /\W(garmin)\.com\//) ||
	       ($href =~ /\W(strava)\.com\//) ||
	       ($href =~ /\W(ridewithgps)\.com\//)) {
	  $td .= "$pre<a title='$title' $target href='$href'>$www::ImageTags{$1}</a>$post";

      } else {
	  if (length($url) > 256) {
	      $url = substr($url, 0, 256).'...';
	  }
	  $td .= "$pre<a title='$title' $target href='$href'>$url</a>$post";
      }
    } else {
      $td .= $line;
    }
  }

  return $td;
}



#*****************************************************************************
#
# Routine:  get_about_date
#
#*****************************************************************************
sub get_about_date {
  my $here = $API::program.'::get_about_date';

  my $days = shift(@_);
  if ($days == 0) {
      return "Today";
  }
  if ($days == 1) {
      return "Yesterday";
  }
  if (abs($days) < 4*30) {
      return "About ".abs(floor($days/7))." weeks";
  }
  if (abs($days) < 2*365) {
      return "About ".abs(floor($days/30))." months";
  }
  return "About ".abs(floor($days/365))." years";
}



#*****************************************************************************
#
# Routine:  view_one_or_more_logs
#
#*****************************************************************************
sub view_one_or_more_logs {
  my $here = $API::program.'::view_one_or_more_logs';

  my $msg = shift(@_);
  my $sth = shift(@_);
  my $order = shift(@_);
  my $edit = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: msg=$msg sth=$sth order=$order edit=$edit\n";
  }

  if ($order =~ /$www::Default{WeekOrder}/) {
    &view_week_logs($msg, $sth, $order, $edit);
    return;
  }

  my $bline = "<tr><th bgcolor=black colspan=4>\n</th></tr>\n";
  my $out = '';

  my @Save_Cols = @www::Default_Display;
  
  my $count = 0;
  my $match = 0;
  my $last = 2;
  while (my $ref = $sth->fetchrow_hashref()) {
    # save first for compares
    if ($count++ == 0) {
      foreach my $col (@Save_Cols) {
        $www::First{$col} = $ref->{$col};
      }
    } else {
      foreach my $col (@Save_Cols) {
        if ($www::First{$col} eq $ref->{$col}) {
          $match++;
        }
      }
    }
    
    $www::Form{Athlete} = $ref->{Athlete};
    $out .= "<table class='table table-striped table-hover table-condensed'><p>\n";
    my $date;
    foreach my $col (@www::LogsCols) {
      my $label;
      if ($API::Label{$col}) {
        $label = $API::Label{$col};
      } else {
        # change underscores to spaces
        ($label = $col) =~ s/_/ /g;
      }
      # based on user preference, choose either English or Metric set
      if ($My::Metric) {
        if (($col eq 'Miles') ||
            ($col eq 'MPH') ||
            ($col eq 'Sec50ydLap') ||
            ($col eq 'MinMile')) {
          next;
        }
      } else {
        if (($col eq 'KMs') ||
            ($col eq 'KPH') ||
            ($col eq 'Sec50mLap') ||
            ($col eq 'MinKM')) {
          next;
        }
      }
      my $cell = $ref->{$col};
      # do not display unused columns
      unless ($cell) {
        next;
      } elsif ($cell eq '00:00:00') {
        next;
      }
      my $td = '';
      my $fade = '';
      my $athlete = $ref->{Athlete};
      my ($year, $month) = split(/-/, $ref->{Month}, 2);
      # convert string month into number
      my $mmm = &get_month_from_string($month);
      
      if ($col eq 'ID') {
        $td = &get_id($ref, $cell, $API::title, $order);
        $td =~ s/<br>/, /g;
        $last = $cell;
        
      } elsif ($col eq 'Athlete') {
        $td = &get_user_select_by_field($API::subtitle, $cell, $col);

      } elsif ($col eq 'Week') {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td .= "<a title='View $cell Week' href=\"$ENV{SCRIPT_NAME}?ViewBy=Athlete:Week&OrderBy=$www::Default{WeekOrder}&Athlete=$athlete&Week=$cell\">$www::ImageTags{$col}</a>";
        
      } elsif ($col eq 'Month') {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td .= "<a title='View $cell Calendar' href=\"$ENV{SCRIPT_NAME}?Edit=Calendar&Athlete:Month&Athlete=$athlete&Year=$year&Month=$mmm\">$www::ImageTags{$col}</a>";
        
      } elsif ($col eq 'Year') {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td .= "<a title='View $mmm months of $cell' href=\"$ENV{SCRIPT_NAME}?Edit=CalYear&Athlete=$athlete&Year=$cell&Month=1:$mmm\">$www::ImageTags{$col}</a>";
        
      } elsif ($col eq 'Notes') {
        if ($edit eq $col) {
          $www::Form{$col} = $ref->{$col};
          my $td .= &append_field_form('Logs', $col, $ref);
          $td =~ s/<td>/<td>/g;
          # start new table
          $out .= $td;
        }
        if ($cell =~ /.+:\/\/.+/) {
          $td = &get_url_td($cell);
        } else {
          $td = $cell;
        }
        # assume multiple spaces indicate pre-formatted text
        if (($msg =~ /$API::subtitle ID/) && ($td =~ /    /)) {
          unless ($td =~ /<br>/) {
            $td =~ s/<br>//g;
            $td = "<pre>$td</pre>";
          }
        }

      } elsif ($col eq 'History') {
        # do not display History when viewing multiple records
        unless ($msg =~ /$API::subtitle ID/) {
          next;
        }
        
        $out .= $bline;
        ##$td = &get_history($API::subtitle, $cell);
        $td = $cell;
        
      } elsif (($col eq 'MPH') ||
               ($col eq 'KPH')) {
        $td = &get_column($API::subtitle, $col, sprintf($www::Format{$col}, $cell), $order, 'Athlete');
        
      } elsif ($col eq 'Date') {
        ##$td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td = &get_date_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td .= '<font class=font_allUsers>';
        $td .= &get_column($API::subtitle, $col, $cell, $order, $col);
        $td .= '</font>';
        
      } elsif (($col eq 'WorkOut') ||
               ($col eq 'Eqmt') ||
               ($col eq 'Leg')) {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        $td .= &get_date_range($ref->{Athlete}, $col, $cell,$ref->{Date});        

      } elsif (grep(/$col/, $www::numeric_field)) {
        # don't use sprintf, rounding changes selection
        #$td = &get_column($API::subtitle, $col, $cell, $order, $field);
        my $value = sprintf($www::Format{Numeric}, $cell);
        if ($www::Format{$col}) {
          $value = sprintf($www::Format{$col}, $cell);
        }
        $td = &get_numeric_column($API::subtitle, $col, $value, $col, 'Athlete');

      } elsif ($col eq 'TTT') {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');
        my $about = &get_about_date($cell);
        $td .= &get_column($API::subtitle, $col, $about, $order, 'Athlete');

      } else {
        $td = &get_column($API::subtitle, $col, $cell, $order, 'Athlete');

      }

      if ($col eq 'Brief') {
         $fade = "
<button type='button' class='btn btn-default' data-toggle='tooltip' data-placement='top' title='Show Notes'
        onclick='\$(\"#Notes-1\").fadeIn();'  value='1'><i class='fa fa-comment-o'></i></button>
<button type='button' class='btn btn-default' data-toggle='tooltip' data-placement='top' title='Hide Notes'
        onclick='\$(\"#Notes-1\").fadeOut();' value='0'><i class='fa fa-comment'></i></button>
";
      }
      if ($col eq 'Notes') {
         ##$out .= "<tr id='$label-1' style='display:none;'><th>$label$fade</th><td>$td</td></tr>\n";
         my $display = 'table-row';
         if ($API::Browser{Tablet}) {
            $display = 'none';
         }
         $out .= "<tr id='$label-1' style='display: $display;'><th>$label$fade</th><td>$td</td></tr>\n";
      } else {
         $out .= "<tr><th>$label$fade</th><td>$td</td></tr>\n";
      }
    }
    $out .= "</table>\n\n";
    
    # side by side tables by default, top-down tables for printing
    my $top_down = '';
    if (($www::My_Prefs{APILayout}) &&
        ($www::My_Prefs{APILayout} ne 'Horiz')) {
      $top_down = '</tr><tr>';
    }
    $out .= "</td>$top_down<td class=td_itemView>";
  }
  $sth->finish();
  ##print STDERR "$here: count=$count, match=$match\n";
  
  # side by side tables
  my $table = "<table class='table'><tr><td>$out</td></tr></table>\n";
  $out = $table;
  if ($count > 1 && $match > 6) {
    print STDERR "$here: count=$count, match=$match\n";
  }
  
  # Add extra links to next & previous
  my $extras = &get_dir_buttons($last, $order);
  &header($msg, $extras);
  print $out;
  $API::bytes += length($out);
  &footer($extras);
}


#*****************************************************************************
#
# Routine:  logs
#
#*****************************************************************************
sub logs {
  my $here = $API::program.'::logs';
  
  my $command = shift(@_);
  my $field = shift(@_);
  my $order = shift(@_);
  if ($API::diag > -2) {
    print STDERR "$here: command=$command, field=$field, order=$order\n";
  }
  
  $API::sql = "SELECT *\n";
  # select the pseudo columns
  foreach my $key (keys %www::SQL) {
    $API::sql .= ", $www::SQL{$key} AS $key\n";
  }

  $API::sql .= "FROM $www::db_logs_table,$www::db_METs_table\n";
  my $only;
  if ($field eq 'All') {
    $only = 'All';
  } else {
    my $only_title;
    my @Fields = split(/:/, $field);
    # use History to filter out 'real' records
    my $where = 'WHERE History IS NOT NULL AND';
    
    $API::sql .= $where;
    foreach my $field (@Fields) {
      $only = $www::Form{$field};
      $API::sql .= '(';
      if ($field eq 'User') {
        # handle single and multiple values
        foreach my $usr (split(/:/, $only)) {
          $API::sql .= "Athlete = \'$usr\' OR
History like \'%Created by $usr%\' OR\n";
        }
        
      } elsif ($field eq 'Miles') {
        $API::sql .= "$field LIKE \"$only%\" OR\n";

      } elsif ($field eq 'Time') {
	$API::sql .= "$field LIKE \"$only\" OR\n";

      } elsif (($field eq 'MPH') ||
	       ($field eq 'KPH')) {
        $API::sql .= "$www::SQL{$field} LIKE \"$only%\" OR\n";

      } elsif ($field eq 'Brief') {
        # Brief can contains special characters
        my @Fix = ('Only');
        $www::Form{Only} = $only;
        &cleanup_text_areas(\@Fix);
        $only = $www::Form{Only};
        $API::sql .= "$www::SQL{$field} = \'$only\' OR\n";

      } elsif (grep(/$field/, keys %www::SQL)) {
        # add normal psuedo-columns
        $API::sql .= "$www::SQL{$field} = \'$only\' OR\n";

      } elsif ($field =~ /^GrEq(\w+)$/) {
        # handle greater than or equal to
        $API::sql .= "$1 >= \'$only\' OR\n";

      } elsif ($field =~ /^LtEq(\w+)$/) {
        # handle less than or equal to
        $API::sql .= "$1 <= \'$only\' OR\n";

      } else {
	# handle single and multiple values
	# NOT when value starts with minus
        foreach my $value (split(/:/, $only)) {
	  if ($value =~ /^\-(.*)$/) {
            $API::sql .= "$field != \"$1\" OR\n";
          } else {
            $API::sql .= "$field = \"$value\" OR\n";
          }
        }
      }
      if ($field =~ /^GrEq(\w+)$/) {
        $only_title .= $only.' or Greater, ';
      } elsif ($field =~ /^LtEq(\w+)$/) {
        $only_title .= $only.' or Lesser, ';
      } else {
        $only_title .= $only.' ';
      }
      $API::sql =~ s/ OR$//g;
      $API::sql .= ") AND\n";
    }
    # add for METS
    $API::sql .= "($www::join_column) AND\n";
    
    $API::sql =~ s/ AND\n$//;
    $only = $only_title;
    chop($only);
  }
  $API::sql .= "\nORDER BY $order;";
  
  # build the list of logs
  my $sth = $API::dbh->prepare($API::sql);
  if ($API::diag > 3) {
    print STDERR "$here: API::sql=$API::sql\n";
  }
  $API::queries++;
  $sth->execute();
  
  if ($command eq 'List') {
     &view_logs_list($only, $order, $sth, $field);
    
  } elsif ($command eq 'Calendar') {
     &view_logs_month($only, $order, $sth, $field);
    
  } else {
    my $msg = '';
    if ($field eq 'ID') {
      if ($www::Form{RegExp}) {
        $msg = "$field $www::Form{RegExp}";
      }
    } else {
      $msg = "$API::subtitle where";
      foreach (split(/:/, $field)) {
        $msg .= " $_ is $www::Form{$_} and";
      }
      $msg =~ s/ and$//;
      $msg .= ": By $order";
    }
    &view_one_or_more_logs($msg, $sth, $order, '');
  }
}


#*****************************************************************************
#
# Routine:  append_logs
#
#*****************************************************************************
sub append_logs {
  my $here = $API::program.'::append_logs';
  
  my $id = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: id=$id\n";
  }
  
  my $sth = $API::dbh->prepare("SELECT *
FROM $www::db_logs_table
WHERE ID = $id;");
  $API::queries++;
  $sth->execute();
  
  &view_one_or_more_logs("Append to $id Form", $sth, 'ID', 'Notes');
}


#*****************************************************************************
#
# Routine:  new_log
#
#*****************************************************************************
sub new_log {
  my $here = $API::program.'::new_log';
  
  my $athlete = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete\n";
  }
  
  my $sth = $API::dbh->prepare("SELECT MAX(ID) AS MAX
FROM $www::db_logs_table
WHERE Athlete = ?;");
  $API::queries++;
  $sth->execute($athlete);
  
  my $ref = $sth->fetchrow_hashref();
  my $max = $ref->{MAX};
  
  $sth = $API::dbh->prepare("SELECT *
FROM $www::db_logs_table
WHERE ID = ?;");
  $API::queries++;
  $sth->execute($max);
  
  $ref = $sth->fetchrow_hashref();
  foreach my $col (@www::LogsCols) {
    if ($ref->{$col}) {
      $www::Help{$col} .= ', e.g.: '.$ref->{$col};
      $www::Last{$col} = $ref->{$col};
    }
  }
}


#*****************************************************************************
#
# Routine:  last_log
#
#*****************************************************************************
sub last_log {
  my $here = $API::program.'::last_log';
  
  my $athlete = shift(@_);
  my $workout = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete, workout=$workout\n";
  }
  
  my $sth = $API::dbh->prepare("SELECT MAX(ID) AS MAX
FROM $www::db_logs_table
WHERE Athlete = ? AND WorkOut = ?;");
  $API::queries++;
  $sth->execute($athlete, $workout);
  
  my $ref = $sth->fetchrow_hashref();
  my $max = $ref->{MAX};
  
  $sth = $API::dbh->prepare("SELECT *
FROM $www::db_logs_table
WHERE ID = ?;");
  $API::queries++;
  $sth->execute($max);
  
  $ref = $sth->fetchrow_hashref();
  foreach my $col (@www::LogsCols) {
    if ($ref->{$col}) {
      $www::Last{$col} = $ref->{$col};
    }
  }
}


#*****************************************************************************
#
# Routine:  get_weather
#
#*****************************************************************************
sub get_weather {
  my $here = $API::program.'::get_weather';
  
  my $city = $www::UserID{City};
  # fix HTML space
  $city =~ s/ /%20/g;
  my $state = $www::UserID{State};
  my $zip = $www::UserID{ZIP};
  my $href;
  if ($www::My_Prefs{elogsWX} eq 'A') {
    $href = "http://www.accuweather.com/us/$state/$city/$zip/city-weather-forecast.asp";

  } elsif ($www::My_Prefs{elogsWX} eq 'T') {
    $href = "http://www.weather.com/weather/today/$zip";

  } elsif ($www::My_Prefs{elogsWX} eq 'M') {
    $href = "http://local.msn.com/weather.aspx?q=$city-$state&zip=$zip";

  } elsif ($www::My_Prefs{elogsWX} eq 'W') {
    $href = "http://www.wunderground.com/cgi-bin/findweather/getForecast?query=$zip";

  } elsif ($www::My_Prefs{elogsWX} eq 'N') {
     $API::Weather = '';
     return '';

  } else {
     $API::Weather = '';
  }

  if ($www::My_Prefs{elogsWX} eq 'A') {
    $API::Weather = $API::AccuWeather;
  } else {
    $API::Weather = "<td>
<iframe name='WX_Bar' src='$href' width='100%' height=2048>NoFrameSupport</iframe>
</td>";
  }
  $API::Weather =~ s/12345/$zip/g;

  return '';
}


#*****************************************************************************
#
# Routine:  edit_log
#
#*****************************************************************************
sub edit_log {
  my $here = $API::program.'::edit_log';
  
  my $id = shift(@_);
  my $errmsg = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: id=$id errmsg=$errmsg\n";
  }
  my $add_me = 0;
  
  &build_distinct_list('Eqmt', $www::db_logs_table, "WHERE Athlete = '$ENV{REMOTE_USER}'");
  $API::Extras = &get_weather();
  
  # get yesterday from MySQL, it's fast & simple
  my $sth = $API::dbh->prepare("SELECT DATE_SUB(CURDATE(), INTERVAL 1 DAY) AS Yesterday;");
  $API::queries++;
  $sth->execute();
  my $ref = $sth->fetchrow_hashref();
  $API::yesterday = $ref->{Yesterday};
  $www::Help{$API::yesterday} = 'Yesterday';

  my $msg;
  my $submit;
  my $date = $www::Form{Date};
  if ($id eq 'NULL') {
    $msg = "New $date $API::subtitle Form";
    (my $value = "Insert New $API::subtitle") =~ s/s$//g;
    $submit = "<input class='$BS::submit' type='submit' name=Submit value='$value'>";
  } else {
    $msg = "Change $API::subtitle $id Form";
    my $value = 'Save Changes';
    $submit = "<div class='btn-group' role='group'>
<input class='$BS::submit' type='submit' name=Submit value='$value'>
<input class='$BS::submit' type='submit' name=SaveAsNew value='Insert As New'>
<input class='$BS::submit' type='submit' name=Delete value='Delete Log $id'>
</div>";
  }
  
  if ($errmsg) {
    # this is the simplest way to make these fields editable
    if ($id eq 'NULL') {
      $www::Form{ID} = '';
    } else {
      # a re-read of the record is required by build_append_only
      my $sth = $API::dbh->prepare("SELECT *
FROM $www::db_logs_table
WHERE ID = $id;");
      $API::queries++;
      $sth->execute();
      
      $ref = $sth->fetchrow_hashref();
    }
  } else {
    if ($id eq 'NULL') {
      $www::Form{Perms} = $www::edit{author};
      # only clear the form if this called from 'Create New ...'
      unless ($www::Form{Edit} eq 'Logs') {
        foreach my $col (@www::LogsCols) {
          $www::Form{$col} = '';
        }
      }
      $www::Form{Athlete} = $ENV{REMOTE_USER};
      $www::Form{Date} = $date;
      ## set defaults for MMTC Race Calendar
      $www::Form{Leg} = 'Race';
      $www::Form{WorkOut} = 'Triathlon';
      ##&new_log($www::Form{Athlete});
      
    } else {
      my $sth = $API::dbh->prepare("SELECT *,
$www::SQL{KMs} AS KMs
FROM $www::db_logs_table
WHERE ID = $id;");
      $API::queries++;
      $sth->execute();
      
      $ref = $sth->fetchrow_hashref();
      $www::Form{Perms} = &editable($ref);
      foreach my $col (@www::LogsCols) {
        $www::Form{$col} = $ref->{$col};
      }
      # build Date list for radio buttons
      @www::List_New = ($API::today, $API::yesterday);
      unless (grep(/$www::Form{Date}/, @www::List_New)) {
        push(@www::List_New, $www::Form{Date});
      }
      # verify permission to Edit
      unless ($www::Form{Perms} > $www::edit{world}) {
        $www::Form{Athlete} = $ENV{REMOTE_USER};
        # force the user to enter/select a date
        $www::Form{Date} = ' ';
        $submit = "<input class='$BS::submit' type='submit' name=SaveAsNew value='Insert As New'>";
        ##&append_logs($id);
        ##return;
      }
      if (($www::Form{Clone}) &&
          ($www::Form{Clone} == $id)) {
        $id = 'NULL';
        $www::Form{Athlete} = $ENV{REMOTE_USER};
        $www::Form{Date} = $ref->{Date};
        $submit = "<input class='$BS::submit' type='submit' name=SaveAsNew value='Add My Report'>";
        $add_me = 1;
      }
      
      &uncleanup_text_areas(\@www::TextAreas);
    }
  }
  
  my $out = "<div class='panel panel-danger'>";

  foreach my $col (@www::LogsCols) {
    my $label = $col;
    if ($API::Label{$col}) {
      $label = $API::Label{$col};
    }

    if ($add_me) {
      if (($col eq 'Athlete') ||
          ($col eq 'Date') ||
          ($col eq 'WorkOut') ||
          ($col eq 'Leg') ||
          ($col eq 'Miles')) {
      # HTML5 input type=date is only supported in Chrome/Safari/Opera
        if(($ENV{HTTP_USER_AGENT} =~ /Chrome/i) ||
           ($ENV{HTTP_USER_AGENT} =~ /Safari/i) ||
           ($ENV{HTTP_USER_AGENT} =~ /Opera/i)) {
          $out .= &build_select_td($label,
                                   $www::Help{Originator},
                                   &build_readonly_text($col, 10));
        } else {
          $out .= &build_select_td($label,
                                   $www::Help{Originator},
                                   "$www::Name{$www::Form{$col}}
<input type=hidden name=$col value=$www::Form{$col}>\n");
        }
        next;
      }
    }

        
    if ($col eq 'ID') {
      next;

    } elsif ($col eq 'Date') {
      # HTML5 input type=date is only supported in Chrome/Safari/Opera
      if(($ENV{HTTP_USER_AGENT} =~ /Chrome/i) ||
         ($ENV{HTTP_USER_AGENT} =~ /Safari/i) ||
         ($ENV{HTTP_USER_AGENT} =~ /Opera/i)) {
          $out .= &build_select_td($label,
                                   $www::Help{$col},
                                   &build_date($col, 0));
      } else {
          my $alt_text = &build_date($col, 0);
          $www::Help{$www::Form{$col}} = 'Default';
          no strict 'refs';
          my $array = "www::List_New";
          ##$www::Form{$col} = $API::today;
          $alt_text .= &build_radio_list($col.':radio', \@$array);
          $out .= &build_select_td($label,
                                   $www::Help{$col},
                                   $alt_text);
      }

    } elsif ($col eq 'Athlete') {
      # HTML5 input type=date is only supported in Chrome/Safari/Opera
      if(($ENV{HTTP_USER_AGENT} =~ /Chrome/i) ||
         ($ENV{HTTP_USER_AGENT} =~ /Safari/i) ||
         ($ENV{HTTP_USER_AGENT} =~ /Opera/i)) {
        $out .= &build_select_td($label,
                                 $www::Help{Originator},
                                 &build_readonly_text($col, 10));
      } else {
        $out .= &build_select_td($label,
                                 $www::Help{Originator},
                                 "$www::Name{$www::Form{$col}}
<input type=hidden name=$col value=$www::Form{$col}>\n");
      }

    } elsif (($col eq 'Leg') ||
             ($col eq 'WorkOut')) {
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_list($col, $www::select_one));
      
    } elsif ($col eq 'Eqmt') {
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_datalist($col, $www::Form{$col}));

    } elsif (grep(/$col/, keys %www::SQL)) {
      next;
      
    } elsif ($col eq 'Miles') {
      if ($My::Metric) {
        $col = 'KMs';
        $www::Form{$col} = $ref->{$col};
      }
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_text($col, 10));
      
    } elsif (grep(/$col/, $www::numeric_field)) {
      # use text, not number, so that math may be used, i.e.: 33+44
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_text($col, 10));
      
    } elsif ($col eq 'Notes') {
      ##$out .= &build_append_only($label, $id, $ref, length($errmsg));
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_textarea($col, ($www::Size{$col}*2)));
      
    } elsif (($col eq 'History') ||
             ($col eq 'Modified')) {
      unless (($id eq 'NULL') ||
              ($www::Form{ID} eq '')) {
        my $td = $ref->{$col};
        $out .= &build_select_td($label,
                                 $www::Help{$col},
                                 &build_readonly_text($col, 20));
      }
    } else {
      $out .= &build_select_td($label,
                               $www::Help{$col},
                               &build_text($col, 20));
    }
  }
  
  my $edit = '';
  unless ($id eq 'NULL') {
    $edit .= &get_id_link($API::subtitle, $id);
  }
  
  my $lowerButtons = "<p>
$submit
$www::Default{ResetForm}
$edit
<p>\n";
  
  my $upperButtons = '';
  ##unless ($id eq 'NULL') {
  $upperButtons = $lowerButtons;

  &header($msg, $API::Extras);
  
  my $locks = "<p>
<button class='btn btn-default' title='Unlock Activity'
        onclick='enableAllObjects();'  name=Lock value='0' checked><i class='fa fa-unlock'></i></button>
<button class='btn btn-default' title='Lock Activity'
        onclick='disableAllObjects();' name=Lock value='1'><i class='fa fa-lock'></i></button>
<button class='btn btn-default' title='Show Notes'
        onclick='\$(\"#Notes-1\").fadeIn();'  value='1'><i class='fa fa-comment-o'></i></button>
<button class='btn btn-default' title='Hide Notes'
        onclick='\$(\"#Notes-1\").fadeOut();' value='0' checked><i class='fa fa-comment'></i></button>
<input type='hidden' id='form_OK' value='true'>
</p>

";
  $locks = '';

  $out = "$errmsg$locks

<form class='form-horizontal' role='form' method=post name='form1' id='form1' action=$ENV{SCRIPT_NAME}>
<input type=hidden name=Field value=Logs>
<input type=hidden name=ID value=$id>
<input type=hidden name=Perms value=$www::Form{Perms}>
$upperButtons
$out
$lowerButtons
</form>
</div>\n";
  print $out;
  $API::bytes += length($out);
  &footer($API::Weather);
}


#*****************************************************************************
#
# Routine:  athlete_day_entry
#
#*****************************************************************************
sub athlete_day_entry {
  my $here = $API::program.'::athlete_day_entry';
  
  my $ref = shift(@_);
  my $cols = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: ref=$ref, cols=$cols\n";
  }
  
  my $id = $ref->{ID};
  
  # use users preference on how to open this window
  my $title = "View Log $id ";
  my $target;
  if (($www::My_Prefs{APIOutWin}) &&
      ($www::My_Prefs{APIOutWin} eq 'Blank')) {
    $title .= 'in a new window';
    $target = 'target=_blank';
  } else {
    $title .= 'in this window';
    $target = 'target=_self';
  }
  
  # add full-info tips
  my $tip_text = '';
  foreach my $col (@www::Default_Display) {
    unless (($col eq 'ID') ||
            (grep(/$col/, $cols))) {
      if ($ref->{$col}) {
        $tip_text .= $col.'=> '.$ref->{$col}.'<br>';
      }
    }
  }

  my $maketip = '<script language="JavaScript" type="text/javascript">';
  $maketip .= "maketip('ViewID_$id','$title','$tip_text');</script>";
  
  my $tip = "onMouseover=\"tip('ViewID_$id')\" onmouseout=\"untip()\"";
  
  my $td = "<a $tip $target href=\"$ENV{SCRIPT_NAME}?Go=$id\">";

  # apply the WorkOut font to the entire entry
  my $class = 'font_'.$ref->{WorkOut};
  # when viewing dual records, show the other guy differently
  if ($www::Form{Athlete} =~ /:/) {
    if ($ref->{Athlete} ne $ENV{REMOTE_USER}) {
      $class = 'div.xoopsCode';
    }
  }
  foreach my $field (split(/:/, $cols)) {
    my $value = $ref->{$field};
    if ($field eq 'Miles') {
      $value = sprintf($www::Format{MPH}, $ref->{Miles});
      if ($My::Metric) {
        $value = sprintf($www::Format{KPH}, $ref->{KMs});
      }
      # display time if no distance was recorded
      if ($value eq '0.0') {
        $value = $ref->{Time};
        $value =~ s/^00://g;
      }
    }
    
    # replace underscores with spaces in enums
    if ($value) {
      $value =~ s/_/ /g;
    }
    $td .= " <font class='$class'>$value</font>";
  }
  $www::MonthTimes{$ref->{WorkOut}}++;
  $www::MonthMiles{$ref->{WorkOut}} += $ref->{Miles};
  $www::MonthMETs{$ref->{WorkOut}} += $ref->{METS};
  $www::MonthSecs{$ref->{WorkOut}} += $ref->{SECS};
  $www::MonthKMs{$ref->{WorkOut}} += $ref->{KMs};
  
  return $maketip.$td;
}


#*****************************************************************************
#
# Routine:  build_extra_summaries
#
#*****************************************************************************
sub build_extra_summaries {
  my $here = $API::program.'::build_extra_summaries';
  
  my $edit = shift(@_);
  my $athlete = shift(@_);
  my $year = shift(@_);
  my $month = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: edit=$edit athlete=$athlete year=$year month=$month\n";
  }
  
  $www::HTML{CalendarTotals} = "<div class=calTotalsDiv><bl>Totals:<br>";
  my $unit = '';
  my $dist = 0;
  my $mets = 0;
  my $entries = 0;
  my $href;
  $www::MonthSecs{ALL} = 0;
  foreach my $key (keys %www::MonthMiles) {
    if ($athlete) {
      $href = "<a href=\"$ENV{SCRIPT_NAME}?Edit=$edit&Athlete=$athlete&Year=$year&Month=1:$month&WorkOut=$key\">$key</a>";
    } else {
      $href = "<a>$key</a>";
    }
    if ($My::Metric) {
      $www::HTML{CalendarTotals} .= "<li>$href: $www::MonthKMs{$key} KMs";
      $dist += $www::MonthKMs{$key};
      $unit = 'KMs';
    } else {
      $www::HTML{CalendarTotals} .= "<li>$href: $www::MonthMiles{$key} Miles";
      $dist += $www::MonthMiles{$key};
      $unit = 'Miles';
    }
    
    my $value = sprintf($www::Format{Numeric}, $www::MonthMETs{$key});
    $www::HTML{CalendarTotals} .= ", $value METs";
    $mets += $www::MonthMETs{$key};
    
    my $time = &get_hh_mm_ss($www::MonthSecs{$key});
    $www::MonthSecs{ALL} += $www::MonthSecs{$key};
    $www::HTML{CalendarTotals} .= ", $www::MonthTimes{$key} Entries, ($time)</li>\n";
    $entries += $www::MonthTimes{$key};
  }
  my $tmets = sprintf($www::Format{Numeric}, $mets);
  my $total = &get_hh_mm_ss($www::MonthSecs{ALL});
  $www::HTML{CalendarTotals} .= "</bl>
<b>All</b>: $dist $unit, $tmets METs, $entries Entries, ($total)\n
</div>\n";
}


#*****************************************************************************
#
# Routine:  fill_calendar
#
#*****************************************************************************
sub fill_calendar {
  my $here = $API::program.'::fill_calendar';
  
  my $athlete = shift(@_);
  my $year = shift(@_);
  my $month = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete year=$year month=$month\n";
  }
  
  # fill the calendar array
  my $mmm = $www::Months[$month - 1];
  $www::Form{Athlete} = $athlete;
  $www::Form{Year} = $year;
  $www::Form{Month} = $year.'-'.$mmm;
  $www::Form{MonthNbr} = $month;
  my $filter = 'Athlete:Year:Month';
  # add WorkOut filter support
  if ($www::Form{WorkOut}) {
    $filter .= ":WorkOut";
  }
  &logs('Calendar', $filter, $www::Default{OrderBy});
}


#*****************************************************************************
#
# Routine:  view_logs_month
#
#*****************************************************************************
sub view_logs_month {
  my $here = $API::program.'::view_logs_month';
  
  my $only = shift(@_);
  my $order = shift(@_);
  my $sth = shift(@_);
  my $field = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: only=$only, order=$order sth=$sth field=$field\n";
  }
  
  my $fonly = &get_full_name($only);
  
  %www::Calendar = ();
  %www::Max = ();
  while (my $ref = $sth->fetchrow_hashref()) {
    my $day = $ref->{DayMonth};
    if ($API::diag > 3) {
      my $id = $ref->{ID};
      print STDERR "$here: id=$id, day=$day\n";
    }
    foreach my $slot (1 .. 10) {
      unless ($www::Calendar{$slot, $day}) {
        my $td = &athlete_day_entry($ref, $www::My_Prefs{elogsCalendar});
        $www::Calendar{$slot, $day} = $td;
        # set the calendar max days based on user's entries
        $www::Max{$day}++;
        if ($www::Max{$day} > $www::max_entries_per_day) {
          $www::max_entries_per_day = $www::Max{$day};
        }
        last;
      }
    }
  }
  $sth->finish();
  
  # build extra summaries
  &build_extra_summaries('CalYear', $www::Form{Athlete}, $www::Form{Year}, $www::Form{MonthNbr});
}


#*****************************************************************************
#
# Routine:  get_calendar_caption
#
#*****************************************************************************
sub get_calendar_caption {
  my $here = $API::program.'::get_calendar_caption';
  
  my $athlete = shift(@_);
  my $year = shift(@_);
  my $month = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete year=$year month=$month\n";
  }
  
  my $href = "$ENV{SCRIPT_NAME}?Edit=Calendar&Athlete=$athlete";
  my $lastMonth = $month - 1;
  my $nextMonth = $month + 1;
  my $lastYear = $year - 1;
  my $nextYear = $year + 1;

  my $prev = "$year&Month=$lastMonth";
  my $pmmm = $www::Months[$lastMonth - 1];
  my $ptitle = 'View '.$www::Months[$lastMonth - 1].' '.$year;
  if ($month == 1) {
      $prev = "$lastYear&Month=12";
      $pmmm = $www::Months[12 - 1];
      $ptitle = 'View '.$www::Months[12 - 1].' '.$lastYear;
  }
  my $pyyy = "$lastYear&Month=$month";
  my $pytitle = 'View '.$www::Months[$month - 1].' '.$lastYear;

  my $next = "$year&Month=$nextMonth";
  my $nmmm = $www::Months[$nextMonth - 1];
  my $ntitle = 'View '.$www::Months[$nextMonth - 1].' '.$year;
  if ($month == 12) {
      $next = "$nextYear&Month=1";
      $nmmm = $www::Months[1 - 1];
      $ntitle = 'View '.$www::Months[1 - 1].' '.$nextYear;
  }
  my $nyyy = "$nextYear&Month=$month";
  my $nytitle = 'View '.$www::Months[$month - 1].' '.$nextYear;

  my $ttitle = 'View Today';
  my $tday = "$API::Date{year}&Month=$API::Date{month}";
  my $out = "<div class='btn-group' role='group'>
<a title='$ptitle' href=\"$href&Year=$prev\" class='btn btn-default' role='button'><i class='fa fa-arrow-left'></i> $pmmm</a>
<a disabled class='btn btn-info' role='button'>$www::Months[$lastMonth]</a>
<a title='$ntitle' href=\"$href&Year=$next\" class='btn btn-default' role='button'>$nmmm <i class='fa fa-arrow-right'></i></a>
</div>\n";

  $out .= "<div class='btn-group' role='group'>
<a title='$pytitle' href=\"$href&Year=$pyyy\" class='btn btn-default' role='button'><i class='fa fa-arrow-left'></i> $lastYear</a>
<a disabled class='btn btn-info' role='button'>$year</a>
<a title='$nytitle' href=\"$href&Year=$nyyy\" class='btn btn-default' role='button'>$nextYear <i class='fa fa-arrow-right'></i></a>
</div>\n";

  return $out;
}


#*****************************************************************************
#
# Routine:  get_calendar_friends
#
#*****************************************************************************
sub get_calendar_friends {
  my $here = $API::program.'::get_calendar_friends';
  
  my $athlete = shift(@_);
  my $year = shift(@_);
  my $month = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete year=$year month=$month\n";
  }
  
  my $last = $year - 1;
  my $next = $year + 1;
  my $href = "$ENV{SCRIPT_NAME}?Edit=Calendar";
  my $out = "<center><div class='btn-group' role='group'>\n";
  
  foreach my $name (sort(values(%www::Name))) {
     my $uid = $www::ByName{$name};
     if (($uid eq $ENV{REMOTE_USER}) ||
         ($uid eq '__DELETED__')) {
        next;
     }
     if ($www::Active{$uid} eq 'Yes') {
        my $class = $BS::reset;
        if ($uid eq $athlete) {
           $class = $BS::other;
        }
        my $title = "View Calendar for $name";
        my ($first, $other) = split(/ /, $name);
        $out .= "<a title='$title' class='$class' href=\"$href&Athlete=$uid&Year=$year&Month=$month\">$uid</a>\n";
        $out .= "<a title='$title &amp Me' class='$BS::reset' href=\"$href&Athlete=$uid:$ENV{REMOTE_USER}&Year=$year&Month=$month\">$first &amp Me</a>\n";
     }
  }
  $out .= "</a></div></center>\n";

  return $out;
}


#*****************************************************************************
#
# Routine:      view_year_calendar
#
#*****************************************************************************
sub view_year_calendar {
  my $here = $API::program.'::view_year_calendar';
  
  my $userid = shift(@_);
  my $year = shift(@_);
  my $months = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: userid=$userid year=$year months=$months\n";
  }
  
  # Add to supress Perl warnings
  $www::CalendarClass{$userid} = $www::BDay{$userid} = $www::Postal_Code{$ENV{REMOTE_USER}};

  # show birthday of displayed athlete
  my $bday = $www::BDay{$userid};
  $www::CalendarClass{$bday} = 'Birthday';
  
  my $out = '';
  my ($m1, $m2) = split(/:/, $months);
  my $mm1 = $www::Months[$m1 - 1];
  my $mm2 = $www::Months[$m2 - 1];
  my $msg = "$mm1 - $mm2 $year";
  &header($msg, '');
  
  foreach my $month ($m1 .. $m2) {
    # minimize the number of days to display
    $www::max_entries_per_day = 1;
    &fill_calendar($userid, $year, $month);

    my $caption = &get_calendar_caption($userid, $year, $month);
    $caption = '';
    my $insert = "$ENV{SCRIPT_NAME}?Edit=NULL";
    # diable inserts when guest or viewing others calendars
    if (($userid ne $ENV{REMOTE_USER}) ||
        ($userid eq $API::Site{guest})) {
      $insert = '';
    }
    
    my $skip = &get_dayofweek($year, $month, 1) - 1;
    if (($www::My_Prefs{APIWeek}) &&
        ($www::My_Prefs{APIWeek} eq 'Monday')) {
      $skip--;
      # can't skip forward a negative number, jump a week instead
      if ($skip == -1) {
        $skip += 7;
      }
    }
    $out .= &calendar_table($year, $month, $caption, $insert, $skip);
  }
  $out .= $www::HTML{CalendarTotals};
  
  $out .= &get_calendar_friends($userid, $year, $m2);
  print $out;
  
  $API::bytes += length($out);
  &footer('');
}


#*****************************************************************************
#
# Routine:  week_fill_days
#
#*****************************************************************************
sub week_fill_days {
  my $here = $API::program.'::week_fill_days';
  
  my $sth = shift(@_);
  my $order = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: sth=$sth order=$order\n";
  }
  
  my $maybe = '';
  unless ($www::Form{Athlete}) {
    $maybe = 'Athlete';
  }
  while (my $ref = $sth->fetchrow_hashref()) {
    ##$www::Form{Athlete} = $ref->{Athlete};
    $www::Form{Week} = $ref->{Week};
    my $tip = &athlete_day_entry($ref, $www::My_Prefs{elogsCalendar});
    my $day = $ref->{Day};
    
    my ($year, $month, $d) = split(/-/, $ref->{Date});
    # calculate the 'nth', from the date, ie: 4th monday
    my $nth = int(($d - 1 ) / 7) + 1;
    my $class = &get_date_class($year, $month, $d, $nth, $day);
    $www::Class{$day} = $class;
    
    my $list = '';
    foreach my $col ($maybe, @www::WeekCols) {
      if ($www::My_Prefs{elogsCalendar} =~ /$col/) {
        # do not display fields in the header
        next;
      }
      my $label;
      if ($API::Label{$col}) {
        $label = $API::Label{$col};
      } else {
        # change underscores to spaces
        ($label = $col) =~ s/_/ /g;
      }
      # only add columns that the user choose (English/Metric)
      unless (grep(/$col/, @www::Default_Display)) {
        next;
      }
      my $cell;
      my $value;
      if ($ref->{$col}) {
        # allow commas to break
          ($cell = $ref->{$col}) =~ s/,/,<wbr>/g;
        if ($col eq 'Athlete') {
          $value = &get_user_select_by_field($API::subtitle, $cell, $col);
          $value = $www::Name{$ref->{$col}};
        } elsif ($col eq 'MPH' || $col eq 'KPH') {
          $value = sprintf($www::Format{$col}, $cell).' '.$col;
        } else {
          $value = $cell;
        }
        $list .= "<div class=weekDiv><a title='$label'>$value</a></div>\n";
      }
    }
    $www::DayInfo{$day} .= "<tr class=$class><td class=td_calendar>
<center>$tip</a></center>
<ul>$list</ul>
</td></tr>\n";
  }
  $sth->finish();
}


#*****************************************************************************
#
# Routine:  view_week_logs
#
#*****************************************************************************
sub view_week_logs {
  my $here = $API::program.'::view_week_logs';
  
  my $msg = shift(@_);
  my $sth = shift(@_);
  my $order = shift(@_);
  my $edit = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: msg=$msg sth=$sth order=$order edit=$edit\n";
  }
  
  foreach my $day (@www::List_Day) {
    $www::DayInfo{$day} = '';
    $www::Class{$day} = '';
  }
  
  &week_fill_days($sth, $order);
  
  my $insert = "$ENV{SCRIPT_NAME}?Edit=NULL";
  my $userid = '';
  if ($msg =~ /Athlete is (.*) and Week/i) {
    $userid = $1;
  }
  # diable inserts when guest or viewing others calendars
  if (($userid ne $ENV{REMOTE_USER}) ||
      ($userid eq $API::Site{guest})) {
     $insert = '';
  }

  my $out = "<table class='table table-striped'>
<tr class=calendarOther>
<td>";
  foreach my $day (@www::List_Day) {
    # get class from week_fill_days
    my $class = $www::Class{$day};

    ##my $date = sprintf("%4.4d-%2.2d-%2.2d", $year, $month, $d);
    my $date = $API::today;
    my $dt;
    if ($insert) {
      (my $title = "Insert $date $API::subtitle") =~ s/s$//g;
      $dt = "<a title='$title' href='$insert&Date=$date'>${day}</a>";
    } else {
      $dt = "<a title='Inserts disabled'>${day}</a>";
    }

    $out .= "<td class='$class' align=left>
<div class='dayDiv'>${dt}</div>
<table class='table table-striped'>
$www::DayInfo{$day}
</table>
</td>";
  }
  $out .= "</td></tr>
</table>\n";
  
  # Add extra links to next & previous
  my $extra = '';
  $extra = &get_week_buttons('ViewBy',
                             $www::Form{Week},
                             $www::Default{WeekOrder});
  &header($msg, $extra);
  &build_extra_summaries('Weekly', '', '', '');
  $out .= $www::HTML{CalendarTotals};
  print $out;
  $API::bytes += length($out);
  
  &read_blog($www::My_Prefs{elogsBlog});
  &footer('');
}


#*****************************************************************************
#
# Routine:  logs_main
#
#*****************************************************************************
sub logs_main {
  my $here = $API::program.'::logs_main';
  
  if ($API::diag > 2) {
    print STDERR "$here:\n";
  }
  
  no strict 'refs';
  
  if (length($www::Form{ListBy})) {
    &logs('List', $www::Form{ListBy}, $www::Form{OrderBy});
  } elsif (length($www::Form{Go})) {
    ##$www::Form{ID} = $www::Form{Go};
    $www::Form{ID} = &regexp_to_sql($www::Form{Go}, $www::db_logs_table);
    &logs('View', 'ID', $www::Default{OrderBy});
  } elsif (length($www::Form{ViewBy})) {
    &logs('View', $www::Form{ViewBy}, $www::Form{OrderBy});
  } elsif (length($www::Form{Edit})) {
    if ($www::Form{Edit} eq 'Report') {
      
      my @Array = (@www::Rpt_Lists,
                   'Athlete');
      @www::List_Athlete = @www::List_User;
      $www::Form{OrderBy} = 'Athlete:WorkOut';
      $www::Form{OrderBy} = 'Date';
      $www::Form{Athlete} = $ENV{REMOTE_USER};

      @www::RptCols = ();
      foreach my $col (@www::LogsCols) {
        # based on user preference, choose either English or Metric set
        if ($My::Metric) {
          if (($col eq 'Miles') ||
              ($col eq 'MPH') ||
              ($col eq 'Sec50ydLap') ||
              ($col eq 'MinMile')) {
            next;
          }
        } else {
          if (($col eq 'KMs') ||
              ($col eq 'KPH') ||
              ($col eq 'Sec50mLap') ||
              ($col eq 'MinKM')) {
            next;
          }
        }
        push(@www::RptCols, $col);
      }
      &report_form(\@www::RptCols, \@Array);
      
    } elsif ($www::Form{Edit} eq 'Login') {
      &login($ENV{REMOTE_ADDR}, '');
      
    } elsif ($www::Form{Edit} eq 'Logout') {
      &logout($ENV{REMOTE_ADDR}, '');
      
    } elsif ($www::Form{Edit} eq 'Account') {
      &edit_account($ENV{REMOTE_USER}, $ENV{REMOTE_ADDR}, '');
      
    } elsif (($www::Form{Edit} eq 'Leg') ||
             ($www::Form{Edit} eq 'WorkOut')) {
      &edit_enum($www::Form{Edit}, '', $ENV{REMOTE_USER});
      
    } elsif ($www::Form{Edit} eq 'Eqmt') {
      &edit_not_enum($www::Form{Edit}, '', $ENV{REMOTE_USER});
      
    } elsif ($www::Form{Edit} eq 'Calendar') {
      my $msg = "";
      if ($www::Form{Athlete} eq $ENV{REMOTE_USER}) {
        $msg = "My Training Calendar";
      } elsif ($www::Form{Athlete} =~ /:/) {
        $msg = "Merged Training Calendar ".$www::Form{Athlete};
      } else {
        $msg = $www::Name{$www::Form{Athlete}}."\'s Training Calendar";
      }
      unless ($www::Form{Year}) {
          $www::Form{Year} = $API::Date{year};
      }
      unless ($www::Form{Month}) {
          $www::Form{Month} = $API::Date{month};
      }
      &read_blog($www::My_Prefs{elogsBlog});
      &view_calendar($www::Form{Athlete}, $www::Form{Year}, $www::Form{Month}, $msg);
      
    } elsif ($www::Form{Edit} eq 'Carousel') {
      &view_carousel($ENV{REMOTE_USER});

    } elsif ($www::Form{Edit} eq 'CalYear') {
      &view_year_calendar($www::Form{Athlete}, $www::Form{Year}, $www::Form{Month});
      
    } elsif ($www::Form{Edit} eq 'BrowseByCount') {
      &browse_by_count;
      
    } elsif ($www::Form{Edit} eq 'Summary') {
      &summary($www::Form{Summary});
      
    } elsif ($www::Form{Edit} eq 'METS') {
      $www::Form{Table} = $www::db_METs_table;
      $www::Form{OrderBy} = 'CompCode';
      $www::Form{Display} = 'CompCode:Activity:Distance:METs:Description';
      $www::Form{ListBy} = 'CompCode';
      $www::Form{GroupBy} = '';
      @www::Names = ();
      @www::Check = ();
      %www::SQL = ();
      &build_report;
      
    } elsif ($www::Form{Edit} eq 'Printenv') {
      &printenv;
      
    } else {
      &edit_log($www::Form{Edit}, '');
    }
    
  } elsif (length($www::Form{Clone})) {
    &edit_log($www::Form{Clone}, '');

  } elsif (length($www::Form{Append})) {
    &append_logs($www::Form{Append});

  } elsif (length($www::Form{Delete})) {
    &delete_enum($www::Form{Delete});

  } elsif (length($www::Form{Prefs})) {
    &prefs_form($www::Form{Prefs}, '');

  } else {
    # default option, Main Menu
    print STDERR "$here: Unknown option, QUERY_STRING=$ENV{QUERY_STRING}\n";
    &splash;
  }
}


#*****************************************************************************
#
# Routine:      verify_new_account (required by the API)
#
#*****************************************************************************
sub verify_new_account {
  my $here = $API::program.'::verify_new_account';
  
  my $userid = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: userid=$userid\n";
  }
  
  my $errors = '';
  
  return $errors;
}


#*****************************************************************************
#
# Routine:      add_time
#
#*****************************************************************************
sub add_time {
  my $here = $API::program.'::add_time';
  
  my $text = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: text=$text\n";
  }
  
  my $lengths = 0;
  my $distance = 0;
  my $tenths = 0;
  my $hours = 0;
  my $minutes = 0;
  my $seconds = 0;
  my $ticks = 0;
  # remove extra windows newlines
  $text =~ s/\r\n/\n/g;

  # look for 2016 Garmin Connect Lap Swimming laps/rests, such as:
  foreach my $line (split(/\n/, $text)) {
    # 1	Freestyle	16	400	8:03.4	8:03.4	2:01	1:46	43	--	--	202	13	74
    # 1 Freestyle	32	800	15:52	15:52	1:59	1:16	42	--	--	387	12	147
    if ($line =~ /Freestyle\s+(\d+)\s+(\d+)\s+(\d+):(\d+)\.(\d+)/) {
      $lengths += $1;
      $distance += $2;
      $minutes += $3;
      $seconds += $4;
      $tenths += $5;
      $www::Form{Miles} = $distance / 1760;
    } elsif ($line =~ /Freestyle\s+(\d+)\s+(\d+)\s+(\d+):(\d+)/) {
      $lengths += $1;
      $distance += $2;
      $minutes += $3;
      $seconds += $4;
      $www::Form{Miles} = $distance / 1760;
    }
  }
  if ($lengths) {
    $www::Form{Climb} = 0;
    $seconds += ($tenths / 10);
    $minutes += ($seconds / 60);
    $seconds  = $seconds % 60;
    $hours   += ($minutes / 60);
    $minutes  = $minutes % 60;
    my $sum = sprintf('%d:%2.2d:%2.2d', $hours, $minutes, $seconds);
    return $sum;
  }

  # for 2016 Garmin Connect, merge all lines and then search
  my $all = '';
  foreach my $line (split(/\n/, $text)) {
    $all .= $line.' ';
  }
  if ($all =~ /\W+(\d+):(\d+):(\d+)\W+Moving Time/) {
    $hours = $1;
    $minutes = $2;
    $seconds = $3;
  } elsif ($all =~ /\W+(\d+):(\d+)\W+Moving Time/) {
    $minutes = $1;
    $seconds = $2;
  } elsif ($all =~ /\W+(\d+):(\d+):(\d+)\W+Time/) {
    $hours = $1;
    $minutes = $2;
    $seconds = $3;
  } elsif ($all =~ /\W+(\d+):(\d+)\W+Time/) {
    $minutes = $1;
    $seconds = $2;
  }
  if ($seconds) {
    my $sum = sprintf('%d:%2.2d:%2.2d', $hours, $minutes, $seconds);
    return $sum;
  }

  foreach my $line (split(/\n/, $text)) {
    ##print STDERR "$here: line=$line\n";

    if ($line =~ /^(\d+):(\d+):(\d+)\.(\d+)/) {
      $hours += $1;
      $minutes += $2;
      $seconds += $3;
      $ticks += $4;
    } elsif ($line =~ /^(\d+):(\d+)\.(\d+)/) {
      $minutes += $1;
      $seconds += $2;
      $ticks += $3;
    } elsif ($line =~ /^(\d+):(\d+):(\d+)/) {
      $hours += $1;
      $minutes += $2;
      $seconds += $3;
    } elsif ($line =~ /^(\d+):(\d+)/) {
      $minutes += $1;
      $seconds += $2;

      # only add Garmin Connect Moving Time if laps are not present
    } elsif ($hours + $minutes + $seconds == 0) {
      if ($line =~ /^Moving Time:\W+(\d+):(\d+):(\d+)/) {
        $hours = $1;
        $minutes = $2;
        $seconds = $3;
      } elsif ($line =~ /^Moving Time:\W+(\d+):(\d+)/) {
        $minutes = $1;
        $seconds = $2;
      }
    }
  }
  print STDERR "$here: ticks=$ticks\n";
  $seconds += $ticks / 100;
  print STDERR "$here: ticks=$ticks\n";
  $ticks = $ticks % 100;
  print STDERR "$here: ticks=$ticks\n";
  if ($ticks >= 50) {
    $seconds++;
  }
  $minutes += ($seconds / 60);
  $seconds  = $seconds % 60;
  $hours   += ($minutes / 60);
  $minutes  = $minutes % 60;
  my $sum = sprintf('%d:%2.2d:%2.2d', $hours, $minutes, $seconds);
  
  return $sum;
}


#*****************************************************************************
#
# Routine:      find_Garmin_Connect
#     my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Distance', ' mi');
#     my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Feels like', '');
#
#*****************************************************************************
sub find_Garmin_Connect {
  my $here = $API::program.'::find_Garmin_Connect';
  
  my $text = shift(@_);
  my $pre = shift(@_);
  my $post = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: text=$text, pre=$pre, post=$post\n";
  }

  my $found = 0;
  # remove extra windows newlines
  $text =~ s/\r\n/\n/g;
  # remove commas
  $text =~ s/,//g;
  foreach my $line (split(/\n/, $text)) {
    ##print STDERR "$here: line=$line\n";
    # handle Garmin Connect Distance, Calories, Elevation Gain
    if ($line =~ /${pre}\W+(\d+\.\d+)${post}/) {
      $found = $1;
    } elsif ($line =~ /${pre}\W+(\d+)${post}/) {
      $found = $1;
    }
  }

  # for 2016 Garmin Connect, merge all lines and then search
  unless ($found) {
    my $all = '';
    foreach my $line (split(/\n/, $text)) {
      $all .= $line.' ';
    }
    if ($all =~ /\W+(\d+\.\d+)${post}\W+${pre}/) {
      $found = $1;
    } elsif ($all =~ /\W+(\d+)${post}\W+${pre}/) {
      $found = $1;
    }
  }
  
  return $found;
}


#*****************************************************************************
#
# Routine:  verify_logs_fields
#
#*****************************************************************************
sub verify_logs_fields {
  my $here = $API::program.'::verify_logs_fields';
  
  if ($API::diag > 2) {
    print STDERR "$here:\n";
  }
  my $errors;
  
  if ($www::Form{WorkOut} eq $www::select_one) {
    my $guess = '';
    if ($www::Form{Notes} =~ /Running/i) {
      $guess = 'Run';
    } elsif ($www::Form{Notes} =~ /Cycling/i) {
      $guess = 'Bike';
    } elsif ($www::Form{Notes} =~ /Ride/i) {
      $guess = 'Bike';
    } elsif ($www::Form{Notes} =~ /Swimming/i) {
      $guess = 'Swim';
    } elsif ($www::Form{Notes} =~ /Hiking/i) {
      $guess = 'Hike';
    } elsif ($www::Form{Notes} =~ /Walk/i) {
      $guess = 'Walk';
    } elsif ($www::Form{Notes} =~ /Run/i) {
      $guess = 'Run';
    }
    if ($guess) {
      $errors .= "<li>Since <i>WorkOut</i> was blank, pre-filling with <b>$guess</b></li>\n";
      $www::Form{WorkOut} = $guess;
    }
  }
  if ($www::Form{WorkOut}) {
    &last_log($www::Form{Athlete}, $www::Form{WorkOut});
  }

  no strict 'refs';
  foreach my $field ('Leg') {
    if (($www::Form{$field} eq $www::select_one) ||
        ($www::Form{$field} eq '')) {
      my $guess = '';

      my $array = "www::List_$field";
      my $disarray = "www::Disabled_$field";
      foreach my $enum (@$array) {
        # do not add enumerations that have been disabled
        unless (grep(/^$enum$/, @$disarray)) {
          ###///$www::Form{Temp} = $field.$enum;
          if ($www::Form{Notes} =~ /$enum/) {
            $guess = $enum;
          }
        }
      }
      if ($guess) {
        $errors .= "<li>Since <i>$field</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{$field} = $guess;
      }
    }
  }

  my $field = 'Leg';
  if ($www::Form{$field} eq $www::select_one) {
    my $guess = '';

    if ($www::Form{Notes} =~ /[4-9]:\d+ AM/i) {
      $guess = 'Morning';
    } elsif ($www::Form{Notes} =~ /[1-2]:\d+ PM/i) {
      $guess = 'Lunch';
    } elsif ($www::Form{Notes} =~ /[5-6]:\d+ PM/i) {
      $guess = 'Evening';
    } elsif ($www::Form{Notes} =~ /[7-9]:\d+ PM/i) {
      $guess = 'Night';
    }
    if ($guess) {
      $errors .= "<li>Since <i>$field</i> was blank, pre-filling with <b>$guess</b></li>\n";
      $www::Form{$field} = $guess;
    }
  }

  if ($www::Form{KMs}) {
    $www::Form{Miles} = $www::Form{KMs} / $My::Mile{km};
  }
  # convert laps into miles
  if ($www::Form{Miles} =~ /^([0-9\.]+)\s*laps/) {
    if ($My::Metric) {
      $www::Form{Miles} = $1 / $My::Mile{lap_50m};
    } else {
      $www::Form{Miles} = $1 / $My::Mile{lap_50yd};
    }
  }
  # allow math entries, for Sandy
  unless ($www::Form{Miles} =~ /^([0-9\.\/\+\-\*\%]+)$/) {
    $errors .= "<li><i>Miles</i> must be numeric or mathmatic</li>\n";
  }
  
  unless ($www::Form{Time}) {
    my $guess = &add_time($www::Form{Notes});
    $errors .= "<li>Since <i>Time</i> was blank, pre-filling with <b>$guess</b></li>\n";
    $www::Form{Time} = $guess;
  }
  
  unless ($www::Form{Miles}) {
    my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Distance', ' mi');
    if (length($guess)) {
      $errors .= "<li>Since <i>Miles</i> was blank, pre-filling with <b>$guess</b></li>\n";
      $www::Form{Miles} = $guess;
    }
  }
  
  if (grep(/Climb/, @www::LogsCols)) {
    unless ($www::Form{Climb}) {
      my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Elev Gain', ' ft');
      if ($guess) {
        $errors .= "<li>Since <i>Climb</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{Climb} = $guess;
      }
    }
  }

  if (grep(/Calories/, @www::LogsCols)) {
    unless ($www::Form{Calories}) {
      my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Calories', ' C');
      if ($guess) {
        $errors .= "<li>Since <i>Calories</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{Calories} = $guess;
      }
    }
  }
  
  if (grep(/Avg_HR/, @www::LogsCols)) {
    unless ($www::Form{Avg_HR}) {
      my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Avg HR', ' bpm');
      if ($guess) {
        $errors .= "<li>Since <i>Avg HR</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{Avg_HR} = $guess;
      }
    }
  }
  
  if (grep(/Avg_HR/, @www::LogsCols)) {
    unless ($www::Form{Max_HR}) {
      my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Max HR', ' bpm');
      if ($guess) {
        $errors .= "<li>Since <i>Max HR</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{Max_HR} = $guess;
      }
    }
  }

  if (grep(/Avg_HR/, @www::LogsCols)) {
    unless ($www::Form{Temp}) {
      my $guess = &find_Garmin_Connect($www::Form{Notes}, 'Feels like', '');
      if ($guess) {
        $errors .= "<li>Since <i>Temp</i> was blank, pre-filling with <b>$guess</b></li>\n";
        $www::Form{Temp} = $guess;
      }
    }
  }
  
  unless ($www::Form{Miles}) {
    if(($www::Form{Calories}) &&
       ($www::Form{Eqmt})) {
      # 0.03 is OK for SchwinnIndoorTrainers
      my $guess = 0.03 * $www::Form{Calories};
      $errors .= "<li>Since <i>Miles</i> was blank, pre-filling with <b>$guess</b></li>\n";
      $www::Form{Miles} = $guess;
    }
  }
  
  # check for mandatory fields
  my @NotNull =
      ('Athlete',
       'Date',
       'Miles',
       'Time');
  foreach my $field (@NotNull) {
    $_ = $www::Form{$field};
    # strip off leading/trailing whitespace
    s/\s*$//;
    s/^\s*//;
    unless (length($_)) {
      $errors .= "<li><b>$field</b> must be specified</li>\n";
    }
  }
  
  my @AutoFill =
      ('Calories',
       'Avg_HR',
       'Max_HR',
       'Temp');
  
  # check for mandatory fields
  foreach my $field (@AutoFill) {
    $_ = $www::Form{$field};
    # strip off leading/trailing whitespace
    s/\s*$//;
    s/^\s*//;
    unless (length($_)) {
      $www::Form{$field} = 'NULL';
    }
  }
  
  # check for pull-downs
  my @PullDowns =
      ('Athlete',
       'WorkOut',
       'Eqmt',
       'Leg');
  foreach my $field (@PullDowns) {
    if ($www::Form{$field} eq $www::select_one) {
      $errors .= "<li><b>$field</b> must be specified</li>\n";
    }
  }
  
  # verify date format, allow one-year ahead
  my $this_year = $API::Date{year}++;
  $errors .= &verify_date('Date');
  $API::Date{year} = $this_year;

  # verify time format
  foreach my $field ('Time') {
    my $time = $www::Form{$field};
    if ($time) {
      # force hours, required by MySQL 5
      if ($time =~ /^\d{1,2}:\d{1,2}$/) {
        $time = "00:$time";
        $www::Form{$field} = $time;
      }
      # allow for HH:MM:SS.fraction format (MySQL 5.0)
      unless ($time =~ /^\d{1,3}:\d{1,2}:\d{1,2}\.?\d*$/) {
        $errors .= "<li><b>$field</b> must be [<i>h+:</i>]<i>mm:ss</i></li>\n";
      }
    }
  }

  # remove extra stuff from Garmin Connect
  $www::Form{Notes} =~ s/Like.*Comments \(0\)//g;
  $www::Form{Notes} =~ s/Previous.*Next.*Player//g;
  $www::Form{Notes} =~ s/\nSummary/\n/g;
  $www::Form{Notes} =~ s/\nSummary/\n/g;
  
  if ($errors) {
    # remove extra windows newlines
    $www::Form{Notes} =~ s/\r\n/\n/g;
    &edit_log($www::Form{ID}, &print_errors($errors));
    return 0;
  } else {
    return 'OK';
  }
}


#*****************************************************************************
#
# Routine:  write_logs
#
#*****************************************************************************
sub write_logs {
  my $here = $API::program.'::write_logs';
  
  if ($API::diag > 2) {
    print STDERR "$here:\n";
  }

  ### for now, always trust the form that the creator has write rights
  $ENV{REMOTE_USER} = $www::Form{Athlete};
  my $created = "$API::today -- Created by $www::Form{Athlete}";
  
  # Use NULL to get a new ID
  # check from insert as new/insert as clone
  if (($www::Form{SaveAsNew}) &&
      ($www::Form{SaveAsNew} =~ /^Insert As/)) {
    $www::Form{ID} = 'NULL';
  }
  
  # check for Delete
  if (($www::Form{Delete}) &&
      ($www::Form{Delete} =~ /^Delete/)) {
    $www::Form{Athlete} = '__DELETED__';
  }
  
  my $ref;
  my $command;
  if ($www::Form{ID} eq 'NULL') {
    $command = 'INSERT';
    $www::Form{History} = $created;
  } else {
    $command = 'UPDATE';
    
    my $sth = $API::dbh->prepare("SELECT *
FROM $www::db_logs_table
WHERE ID = $www::Form{ID};");
    $API::queries++;
    $sth->execute();
    
    $ref = $sth->fetchrow_hashref();
    # verify permission to Edit
    unless (&editable($ref) > $www::edit{world}) {
      my $error = "Permission denied, you are not allowed to edit ID $www::Form{ID} in $www::db_logs_table";
      &warning($error);
      return 0;
    }
    
    $www::Form{History} = $ref->{History};
    $www::Form{History} .= "<br>
$API::today -- Modified by $www::Form{Athlete}";
    
    my $changes = 0;
    foreach my $col (@www::LogsCols) {
      if (($col eq 'History') ||
          ($col eq 'Modified') ||
          (grep(/$col/, keys %www::SQL))) {
        next;
      }
      
      if (($www::Form{$col} eq 'NULL') && ($ref->{$col} eq '')) {
        next;

      } elsif ($www::Form{$col} ne $ref->{$col}) {
        $changes++;
        $www::Form{History} .= ", $col changed";
        unless (($col eq 'Notes') ||
                ($col eq 'Documents')) {
          my $old = $ref->{$col};
          my $new = $www::Form{$col};
          
          if ($col eq 'Notify') {
            &add_multiple_user_history($old, $new);
          } else {
            foreach my $item (\$old, \$new) {
              if ($$item eq '') {
                $$item = $www::undefined;
              }
            }
            $www::Form{History} .= " from $old to $new";
          }
        }
      }
    }
    unless ($changes) {
      &logs('View', 'ID', $www::Default{OrderBy});
      print STDERR "$here: No changes made to $www::Form{ID}\n";
      return 0;
    }
  }
  
  if ($API::diag > 3) {
    foreach my $col (@www::LogsCols) {
      print STDERR "$col = $www::Form{$col}\n";
    }
  }
  
  # insert the records by column
  $API::sql = '';
  if ($www::Form{ID} eq 'NULL') {
    $API::sql = "$command INTO $www::db_logs_table SET ";
  } else {
    $API::sql = "$command $www::db_logs_table SET ";
  }
  foreach my $col (@www::LogsCols) {
    if (($col eq 'Modified') ||
        (grep(/$col/, keys %www::SQL))) {
      next;
    }
    # don't quote numerics, rounding will not occur
    if (grep(/$col/, $www::numeric_field)) {
      if ($www::Form{$col}) {
        $API::sql .= "$col = $www::Form{$col},";
      } else {
        $API::sql .= "$col = 0,";
      }
    } else {
      $API::sql .= "$col = ".$API::dbh->quote("$www::Form{$col}").',';
    }
  }
  if ($www::Form{ID} eq 'NULL') {
    $API::sql =~ s/,$/;/g;
    # added for new MySQL
    $API::sql =~ s/ID = 'NULL',/ID = NULL,/g;
  } else {
    $API::sql =~ s/,$//g;
    $API::sql .= "WHERE ID = $www::Form{ID};";
  }
  if ($API::diag > 3) {
    print STDERR "$here: sql=$API::sql\n";
  }
  $API::dbh->do($API::sql);
  
  print STDERR "$here: $ENV{REMOTE_USER}, Athlete=$www::Form{Athlete}, $command, $www::Form{ID}\n";
  
  &set_new_id($www::db_logs_table);
  
  return 1;
}


#*****************************************************************************
#
# Routine:  process_logs_form
#
#*****************************************************************************
sub process_logs_form {
  my $here = $API::program.'::process_logs_form';
  
  if ($API::diag > 2) {
    print STDERR "$here:\n";
  }
  
  &read_input;
  if ($www::Form{Field} eq 'Logs') {
    if (&verify_logs_fields) {
      &cleanup_text_areas(\@www::TextAreas);
      if (&write_logs) {
        my $id = $www::Form{ID};
        &logs('View', 'ID', $www::Default{OrderBy});
      }
    }
  } elsif ($www::Form{Field} =~ /^Logs:(\w+)$/) {
    &process_append($1, $www::db_logs_table);
    &logs('View', 'ID', $www::Default{OrderBy});
  } else {
    $www::numeric_field = 'Miles:METS';
    &process_form;
  }
}


#*****************************************************************************
#
# Routine:      get_date_range
#
#  inputs:      Athlete, field
# outputs:      userid
#
#*****************************************************************************
sub get_date_range {
  my $here = $API::program.'::get_date_range';
  
  my $athlete = shift(@_);
  my $field = shift(@_);
  my $value = shift(@_);
  my $enddate = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: athlete=$athlete, field=$field\n";
  }
  
  my $sth = $API::dbh->prepare("SELECT
SUM(Miles) AS AllMiles,
SEC_TO_TIME(ROUND(SUM(TIME_TO_SEC(Time)) / SUM(Miles))) AS MinPerMile,
SUM(Miles) / (SUM(TIME_TO_SEC(Time)) / 3600) AS MPH,
DATEDIFF(MAX(Date), MIN(Date)) AS Days
FROM $www::db_logs_table
WHERE Athlete = '$athlete'
AND $field = '$value'
AND Date <= '${enddate}'
AND DATEDIFF('${enddate}', Date) <= ?
GROUP BY Athlete;");
  $API::queries++;
  $sth->execute($www::My_Prefs{elogsRange});
  
  my $ref = $sth->fetchrow_hashref();
  my $td = sprintf($www::Format{DateRange},
                   $ref->{AllMiles}, $ref->{Days}, $ref->{MinPerMile}, $ref->{MPH});
  
  return $td;
}


#*****************************************************************************
#
# Routine:      get_active_users
#
#  inputs:      days
# outputs:      userid
#
#*****************************************************************************
sub get_active_users {
  my $here = $API::program.'::get_active_users';
  
  my $days = shift(@_);
  if ($API::diag > 2) {
    print STDERR "$here: days=$days\n";
  }
  
  my $sth = $API::dbh->prepare("SELECT Athlete, TO_DAYS(NOW()) - TO_DAYS(MAX(Date)) AS Last
FROM $www::db_logs_table
WHERE Athlete IS NOT NULL AND Date <= NOW()
GROUP BY Athlete;");
  $API::queries++;
  $sth->execute();
  
  while (my $ref = $sth->fetchrow_hashref()) {
    my $userid = $ref->{Athlete};
    $www::Active{$userid} = 'No';
    my $last = $ref->{Last};
    if ($API::diag > 3) {
      print STDERR "$here: userid=$userid, last=$last\n";
    }
    if ($last <= $days) {
      $www::Active{$userid} = 'Yes';
    }
  }
}


#*****************************************************************************
#
# Routine:	get_image_types
#
#*****************************************************************************
sub get_image_types {
   my $here = $API::program.'::get_image_types';

   my $ip = shift(@_);
   if ($API::diag > 1) {
      print STDERR "$here: ip=$ip\n";
   }

   my $sth = $API::dbh->prepare("SELECT Type, COUNT(Type) AS Count
FROM uimagez
GROUP BY Type
ORDER BY Type;");
   $API::queries++;
   $sth->execute();

   my $any = '';
   while (my $ref = $sth->fetchrow_hashref()) {
     my $value = $ref->{Type};
     if ($value =~ /Figure/i) {
       if ($www::Gender{$ENV{REMOTE_USER}}) {
         if ($www::Gender{$ENV{REMOTE_USER}} eq 'Female') {
           next;
         }
       }
     }
     if ($ref->{Count}) {
       $www::elogsImages{$value} = "$value ($ref->{Count})";
       $any .= $value.':';
     }
   }
   chop($any);
   # change underscores to spaces
   (my $label = $any) =~ s/:/, /g;
   $www::elogsImages{$any} = "Any ($label)";
}

#*****************************************************************************
#
# Routine:  main
#
#*****************************************************************************
$API::today = &get_date('Today');
$www::Help{$API::today} = 'Today';

$API::dbh = &open_database($API::Site{connect},
                           $API::Site{account},
                           $API::Site{passwd});

$API::MMTC{connect} = "DBI:mysql:database=$API::MMTC{sql_db};host=$API::MMTC{sql_host}";
$API::mmtc_dbh = &open_database($API::MMTC{connect},
                                $API::MMTC{account},
                                $API::MMTC{passwd});

# get userid by IP address, auto-logout after 7 days
$ENV{REMOTE_USER} = &get_userid_by_IP($ENV{REMOTE_ADDR}, 7);
$www::Name{$ENV{REMOTE_USER}} = $ENV{REMOTE_USER};

&read_orgchart_table;
&get_active_users(90);

# not sure if this can be used here
$www::SQL{Week} = 'CONCAT(YEAR(DATE), \'-\', WEEK(Date, 0))';
# week defined as Monday-Sunday
if (($www::My_Prefs{APIWeek}) &&
    ($www::My_Prefs{APIWeek} eq 'Monday')) {
  #Mode First DoW  Range   Week 1 is the first week
  #  0     Sunday  0-53    with a Sunday in this year
  #  1     Monday  0-53    with more than 3 days this year
  #  2     Sunday  1-53    with a Sunday in this year
  #  3     Monday  1-53    with more than 3 days this year
  #  4     Sunday  0-53    with more than 3 days this year
  #  5     Monday  0-53    with a Monday in this year
  #  6     Sunday  1-53    with more than 3 days this year
  #  7     Monday  1-53    with a Monday in this year
  $www::SQL{Week} = 'CONCAT(YEAR(DATE), \'-\', WEEK(Date, 5))';
  @www::List_Day = qw(Monday Tuesday Wednesday Thursday Friday Saturday Sunday);
}
my $zip = $www::Postal_Code{$ENV{REMOTE_USER}};
&set_user_prefs($www::My_Prefs{elogsBgColor});
# based on user preference, choose either English or Metric set
if ($www::My_Prefs{elogsUnits} eq 'KPH') {
  $My::Metric = 1;
} else {
  $My::Metric = 0;
}
$API::Label{Temp} = 'Temp, '.$www::My_Prefs{elogsTemp};

&get_image_types($ENV{REMOTE_ADDR});

if ($ENV{REMOTE_USER} ne $API::Site{guest}) {
  $www::HTML{User_CSS} = "/css/$www::elogsBgColor{$www::My_Prefs{elogsBgColor}}.css";
} else {
  $www::HTML{User_CSS} = "/css/$ENV{REMOTE_USER}.css";
}

$API::Browser{Tablet} = 0;
if (($ENV{HTTP_USER_AGENT} =~ /Android/i) ||
    ($ENV{HTTP_USER_AGENT} =~ /Mobile/i) ||
    ($ENV{HTTP_USER_AGENT} =~ /Silk/i)) {
   $API::Browser{Tablet} = 1;
   $www::My_Prefs{elogsCH} = $www::Prefs_Defaults{elogsCH};
}

&build_buttons;

# always build these lists, they're needed very often
@www::List_Athlete = ();
@www::List_User = ();
&create_list_of_users;

@www::Dynamic_Lists =
    ('Date',
     'Eqmt',
     'WorkOut',
     'Leg');
@www::Enum_Lists =
    ('Week',
     'Month',
     'Day',
     'Year',
     @www::Dynamic_Lists);

@www::Rpt_Lists =
    ('WorkOut',
     'Eqmt',
     'Leg',
     'Year',
     'Month',
     'Day');

@My::Default_Display = ();
foreach my $col (@www::Default_Display) {
  # based on user preference, choose either English or Metric set
  if ($My::Metric) {
    if (($col eq 'Miles') ||
        ($col eq 'MPH') ||
        ($col eq 'Sec50ydLap') ||
        ($col eq 'MinMile')) {
      next;
    }
  } else {
    if (($col eq 'KMs') ||
        ($col eq 'KPH') ||
        ($col eq 'Sec50mLap') ||
        ($col eq 'MinKM')) {
      next;
    }
  }
  push(@My::Default_Display, $col);
}
@www::Default_Display = @My::Default_Display;

&create_dynamic_lists($www::db_logs_table);
foreach my $col ('Week',
                 'Month',
                 'Year',
                 'Date') {
  &build_distinct_list($col, $www::db_logs_table, '');
}
&build_distinct_list('Eqmt', $www::db_logs_table, '');

$www::HTML{Append} = "<br>
<li class=appendList>Added $API::today by $www::Name{$ENV{REMOTE_USER}}<br>
<div class=AppendDiv>\n";
$www::HTML{AppendEnd} = "</div></li>\n";

if ($API::Browser{Tablet}) {
  @www::Enum_Lists = ('WorkOut', 'Leg');
  $www::HTML{DayFormat} = '';
}
if ($ENV{REQUEST_METHOD} eq 'GET') {
  if ($ENV{QUERY_STRING} eq '') {
    # default option, Main Menu
    &splash;
  } else {
    &read_input;
    &logs_main;
  }
} elsif ($ENV{REQUEST_METHOD} eq 'POST') {
  &process_logs_form;
} elsif ($ENV{REQUEST_METHOD} eq 'NULL') {
  # added to allow require to specify no action
} else {
  die "Bad request method = $ENV{REQUEST_METHOD}\n";
}

# Disconnect from the database.
$API::dbh->disconnect();

# Disconnect from the database.
$API::mmtc_dbh->disconnect();

use integer;
my $KB = $API::bytes / 1024;
my $byebye = sprintf("%s:%s %s:%s:%s:%4.4d Kb:%3.3d Queries\n", $here, $API::revision, $ENV{REMOTE_USER}, $ENV{REMOTE_PORT}, $ENV{REQUEST_METHOD}, $KB, $API::queries); 
print STDERR $byebye;
