require 'tty-prompt' require 'rainbow/refinement' using Rainbow require_relative 'csv_reader' module Twstats class Runner def initialize(file = nil) # Load interactive console @prompt = TTY::Prompt.new puts WELLCOME_MESSAGE.bright # Ask for the csv file file ||= @prompt.ask('Specify the CSV file from a Teamwork time log export', default: 'exportTimeLog.csv') do |input| input.modify :chomp end @csv = CSVReader.new(file) loop do option = @prompt.select("Choose an option", Twstats::MENU_CHOICES, cycle: true) case option when :stats show_stats_menu when :billing billing when :quit break else puts 'Option not recognized!' end end end def billing ## Takes care of using the current CSV file to be imported into timesheets. # Makes it easier to do it than manually going through all of them TimeSheetExport.new(@csv, @prompt).execute end def show_stats_menu loop do option = @prompt.select("Select what time logging stats you want to see", Twstats::STATS_MENU_CHOICES, cycle: true) case option when :projects, :people, :tags show_stats option when :fullstats show_full_stats when :weekly show_weekly_report when :back return else puts 'Option not recognized' end end end def show_stats(obj, filter_billable = nil) puts "Time logged vs #{obj.to_s}:" if filter_billable.nil? toshow = {} max = 0 filter_billable ||= @prompt.yes?('Do you want to filter billable and non-billable time?', default: true) @csv.send(obj).each do |element| toshow[element] = @csv.get_total_time(obj, element, filter_billable) max = element.size if max < element.size end toshow.sort_by{ |k,v| v}.reverse.to_h.each do |k,v| if filter_billable if v[0].zero? puts " - #{k.ljust(max,' ').bright.blue} | #{"Non-billable:".bright} #{v[1].round(2)}"# unless v[1].zero? else puts " - #{k.ljust(max,' ').bright.blue} | #{"Billable:".ljust(13," ").bright} #{v[0].round(2)} (#{((v[0]/(v[0]+v[1]))*100).round(2)} %)" puts " #{"".ljust(max,' ').bright.blue} | #{"Non-billable:".bright} #{v[1].round(2)}" unless v[1].zero? end else puts " - #{k.ljust(max,' ').bright.blue} | #{v.round(2)}" unless v.zero? end end show_not_tagged_section false end def ranking_from_not_tagged(list) result = {} list[:list].each do |log| result[log.who] ||= 0 result[log.who] += log.decimal_time end result.sort_by{|k,v| v}.reverse.to_h end def print_table(log_list) 167.times{print '-'} puts "\n|"+'Date'.center(12,' ').bright+'|'+'Time'.center(6,' ').bright+'|'+'Who'.center(20, ' ').bright+ '|'+'Description'.center(78,' ').bright+'|'+'Tags'.center(24,' ').bright+'|'+'Task'.center(20, ' ').bright+'|' 167.times{print '-'} puts "" log_list.each do |log| puts table_line(log) end 167.times{print '-'} puts "" end def table_line(log) "|"+"#{log.date.strftime('%d/%m/%Y')}".truncate(10).center(12,' ')+ '|'+"#{log.decimal_time.round(2)}".truncate(4).center(6,' ')+ '|'+"#{log.who}".truncate(18).center(20, ' ')+ '|'+"#{log.description}".truncate(76).ljust(78,' ')+ '|'+"#{log.tags.join(', ')}".truncate(22).center(24,' ')+ '|'+"#{log.task}".truncate(18).center(20, ' ')+'|' end def show_full_stats billable, non_billable = @csv.get_total_time(:all, nil, true) to_bill = @prompt.yes? 'Do you want to calculate the billed amount?' if to_bill amount_per_hour = @prompt.ask 'What is the hourly rate?', default: 46 end section "Total time spent" puts " - Billable ".bright.blue + " | " + billable.round(2).to_s puts " - Non-billable ".bright.blue + " | " + non_billable.round(2).to_s if to_bill puts " - Billed amount ".bright.blue + " | " + (billable*amount_per_hour).round(2).to_s + " € " end section "People involved" show_stats :people, true section "Tags used" show_stats :tags, true show_not_tagged_section(true) section "Usefull metrics" show_metrics end def show_not_tagged_section(table) not_tagged = @csv.not_tagged_tasks unless not_tagged[:list].empty? section("Tasks not tagged in this report") puts "A total of #{not_tagged[:list].size} logs have not been tagged:" puts " - A total time of #{not_tagged[:total_time]} is not tagged properly." puts " - Impact by employee: " ranking_from_not_tagged(not_tagged).each do |user, time| puts "\t - #{user.ljust(30,' ').bright.blue} | #{time}" end table = @prompt.yes?('Do you want to see what tasks have not been tagged?', default: true) if table.nil? if table print_table not_tagged[:list] end end end def section(text) puts "" puts (" "+text+" ").center(70,"*").bright end def show_metrics mean = @csv.logs.inject(0) {|sum, log| sum + log.decimal_time }.to_f/@csv.logs.size mean_per_task = @csv.mean_time_per_task puts " - Mean time logged: ".bright.blue + mean.round(2).to_s puts " - Mean time per task: ".bright.blue + mean_per_task.round(2).to_s end def show_weekly_report unless @csv.is_weekly? puts 'The CSV file provided has logged times that differ more than a week.' unless @prompt.ask 'Are you sure you want to continue?', default: true return end end hours = @prompt.ask 'What is the weekly amount of hours worked?', default: 40, convert: :float info = {} @csv.people.each do |person| billable, non_billable = @csv.get_total_time(:people, person, true) info[person] = {rate: billable * 100 / hours, not_billed: hours - billable - non_billable, billable: billable, non_billable: non_billable } end info.sort_by{|x,v| v[:rate] }.reverse.each do |person, data| puts " - " + person.bright.blue puts "\tBillable time: ".ljust(20, ' ').bright + data[:billable].round(2).to_s puts "\tBillable rate: ".ljust(20, ' ').bright + data[:rate].round(2).to_s + ' % ' puts "\tNot logged time: ".ljust(20, ' ').bright + data[:not_billed].round(2).to_s end end end end