Finish first version of ruling timer.
This commit is contained in:
commit
1be1ef10eb
|
@ -0,0 +1,8 @@
|
||||||
|
.bundle/
|
||||||
|
log/*.log
|
||||||
|
pkg/
|
||||||
|
test/dummy/db/*.sqlite3
|
||||||
|
test/dummy/db/*.sqlite3-journal
|
||||||
|
test/dummy/log/*.log
|
||||||
|
test/dummy/storage/
|
||||||
|
test/dummy/tmp/
|
|
@ -0,0 +1,15 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
|
# Declare your gem's dependencies in sync_ntnu_personal_data.gemspec.
|
||||||
|
# Bundler will treat runtime dependencies like base dependencies, and
|
||||||
|
# development dependencies will be added by default to the :development group.
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
# Declare any dependencies that are still in development here instead of in
|
||||||
|
# your gemspec. These might include edge Rails or gems from your path or
|
||||||
|
# Git. Remember to move these dependencies to your gemspec before releasing
|
||||||
|
# your gem to rubygems.org.
|
||||||
|
|
||||||
|
# To use a debugger
|
||||||
|
# gem 'byebug', group: [:development, :test]
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright 2021 BOHUNG,CHIU
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,32 @@
|
||||||
|
begin
|
||||||
|
require 'bundler/setup'
|
||||||
|
rescue LoadError
|
||||||
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'rdoc/task'
|
||||||
|
|
||||||
|
RDoc::Task.new(:rdoc) do |rdoc|
|
||||||
|
rdoc.rdoc_dir = 'rdoc'
|
||||||
|
rdoc.title = 'SyncNtnuPersonalData'
|
||||||
|
rdoc.options << '--line-numbers'
|
||||||
|
rdoc.rdoc_files.include('README.md')
|
||||||
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||||
|
end
|
||||||
|
|
||||||
|
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
||||||
|
load 'rails/tasks/engine.rake'
|
||||||
|
|
||||||
|
load 'rails/tasks/statistics.rake'
|
||||||
|
|
||||||
|
require 'bundler/gem_tasks'
|
||||||
|
|
||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.libs << 'test'
|
||||||
|
t.pattern = 'test/**/*_test.rb'
|
||||||
|
t.verbose = false
|
||||||
|
end
|
||||||
|
|
||||||
|
task default: :test
|
|
@ -0,0 +1,206 @@
|
||||||
|
class Admin::RulingTimersController < OrbitAdminController
|
||||||
|
include Admin::RulingTimersHelper
|
||||||
|
before_action ->(module_app = @app_title) { set_variables module_app }
|
||||||
|
before_action :set_weekdays
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
@app_title = "ruling_timer"
|
||||||
|
end
|
||||||
|
def index
|
||||||
|
user = current_user
|
||||||
|
@ruling_timer_temp = RulingTimerTemp.where(:user=>user).first
|
||||||
|
@ruling_timer_history = []
|
||||||
|
all_count = 0
|
||||||
|
per = 10
|
||||||
|
page = params[:page].to_i
|
||||||
|
if @ruling_timer_temp
|
||||||
|
@ruling_timer_history = RulingTimerHistory.where(:user=>user).desc(:created_at)
|
||||||
|
all_count += (1 + @ruling_timer_history.count)
|
||||||
|
if page <= 1
|
||||||
|
@ruling_timer_history = @ruling_timer_history.page(0).per(per - 1)
|
||||||
|
else
|
||||||
|
@ruling_timer_history = @ruling_timer_history.limit(per).skip(per * page - 1)
|
||||||
|
@ruling_timer_temp = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ruling_pager = RulingPager.new(:count=>all_count,:page=>page,:per=>per)
|
||||||
|
end
|
||||||
|
def timer_management
|
||||||
|
@timer_temps = RulingTimerTemp.all.page(params[:page]).per(15)
|
||||||
|
@active_users = @timer_temps.map{|t| t.user}
|
||||||
|
end
|
||||||
|
def task_management
|
||||||
|
@tasks = RulingTimerTask.all.page(params[:page]).per(10)
|
||||||
|
end
|
||||||
|
def work_history
|
||||||
|
user = User.find(params[:id]) rescue nil
|
||||||
|
@ruling_timer_temp = RulingTimerTemp.where(:user=>user).first
|
||||||
|
@ruling_timer_history = []
|
||||||
|
all_count = 0
|
||||||
|
per = 10
|
||||||
|
page = params[:page].to_i
|
||||||
|
if @ruling_timer_temp
|
||||||
|
@ruling_timer_history = RulingTimerHistory.where(:user=>user).desc(:created_at)
|
||||||
|
all_count += (1 + @ruling_timer_history.count)
|
||||||
|
if page <= 1
|
||||||
|
@ruling_timer_history = @ruling_timer_history.page(0).per(per - 1)
|
||||||
|
else
|
||||||
|
@ruling_timer_history = @ruling_timer_history.limit(per).skip(per * page - 1)
|
||||||
|
@ruling_timer_temp = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@ruling_pager = RulingPager.new(:count=>all_count,:page=>page,:per=>per)
|
||||||
|
@user_name = user.name
|
||||||
|
render "index"
|
||||||
|
end
|
||||||
|
def edit_temp_timer
|
||||||
|
@timer = RulingTimerTemp.find(params[:id]) rescue nil
|
||||||
|
if @timer.nil?
|
||||||
|
redirect_to admin_ruling_timers_path and return
|
||||||
|
end
|
||||||
|
render "edit_timer"
|
||||||
|
end
|
||||||
|
def edit_timer
|
||||||
|
@timer = RulingTimerHistory.find(params[:id]) rescue nil
|
||||||
|
if @timer.nil?
|
||||||
|
redirect_to admin_ruling_timers_path and return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def update_timer
|
||||||
|
@timer = nil
|
||||||
|
if params[:type] == "temp"
|
||||||
|
@timer = RulingTimerTemp.find(params[:id])
|
||||||
|
else
|
||||||
|
@timer = RulingTimerHistory.find(params[:id])
|
||||||
|
end
|
||||||
|
@timer_params = timer_params
|
||||||
|
date = @timer.date.split(" ")[0]
|
||||||
|
@timer_params[:work_times] = @timer_params[:work_times].to_a.map do |t|
|
||||||
|
if t.blank?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
DateTime.parse("#{date} #{t}#{@timer.time_offset}").utc
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
@timer.update_attributes(@timer_params)
|
||||||
|
@timer.fix_work_times
|
||||||
|
redirect_to params[:referer_url]
|
||||||
|
end
|
||||||
|
def add_task
|
||||||
|
@task = RulingTimerTask.new
|
||||||
|
@users = User.all.where(:user_name.ne=>"rulingcom")
|
||||||
|
end
|
||||||
|
def edit_task
|
||||||
|
@task = RulingTimerTask.find(params[:id])
|
||||||
|
@users = User.all.where(:user_name.ne=>"rulingcom")
|
||||||
|
end
|
||||||
|
def create_task
|
||||||
|
@task = RulingTimerTask.create(task_params)
|
||||||
|
redirect_to params[:referer_url]
|
||||||
|
end
|
||||||
|
def update_task
|
||||||
|
@task = RulingTimerTask.find(params[:id])
|
||||||
|
@task.update_attributes(task_params)
|
||||||
|
redirect_to params[:referer_url]
|
||||||
|
end
|
||||||
|
def delete_task
|
||||||
|
if params[:confirm_delete]
|
||||||
|
RulingTimerTask.where(:id=>params[:id]).destroy
|
||||||
|
render :json => {:success=>true}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def view_task
|
||||||
|
@task = RulingTimerTask.find(params[:id])
|
||||||
|
end
|
||||||
|
def delete_history
|
||||||
|
if params[:confirm_delete]
|
||||||
|
if params[:type] == 'temp'
|
||||||
|
RulingTimerTemp.where(:id=>params[:id]).destroy
|
||||||
|
else
|
||||||
|
RulingTimerHistory.where(:id=>params[:id]).destroy
|
||||||
|
end
|
||||||
|
render :json => {:success=>true}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def add_history
|
||||||
|
@user = nil
|
||||||
|
if params[:id] == "current_user"
|
||||||
|
params[:id] = current_user.id.to_s
|
||||||
|
@user = current_user
|
||||||
|
else
|
||||||
|
@user = User.find(params[:id])
|
||||||
|
end
|
||||||
|
@history = RulingTimerHistory.new(:user_id=>params[:id])
|
||||||
|
end
|
||||||
|
def create_history
|
||||||
|
@history_params = history_params
|
||||||
|
user = User.find(@history_params[:user_id])
|
||||||
|
@old_historys = RulingTimerHistory.where(:date=>@history_params[:date],:user=>user).to_a + RulingTimerTemp.where(:date=>@history_params[:date],:user=>user).to_a
|
||||||
|
if @old_historys.count == 0
|
||||||
|
if RulingTimerTemp.where(:user=>user).count == 0
|
||||||
|
@history = RulingTimerTemp.create(@history_params)
|
||||||
|
else
|
||||||
|
@history = RulingTimerHistory.create(@history_params)
|
||||||
|
end
|
||||||
|
@history.recalc_all
|
||||||
|
@history.fix_work_times
|
||||||
|
else
|
||||||
|
@old_history = @old_historys.first
|
||||||
|
@old_history.merge_work_times(@history_params[:work_times])
|
||||||
|
end
|
||||||
|
redirect_to params[:referer_url]
|
||||||
|
end
|
||||||
|
def history_params
|
||||||
|
history_params = params.require(:ruling_timer_history).permit!
|
||||||
|
date = history_params[:date]
|
||||||
|
time_offset = history_params[:time_offset]
|
||||||
|
history_params[:work_times] =[] if history_params[:work_times].nil?
|
||||||
|
first_time = history_params[:work_times].first
|
||||||
|
first_time = "00:00:00" if first_time.blank?
|
||||||
|
history_params[:work_times] = history_params[:work_times].map do |t|
|
||||||
|
if t.blank?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
DateTime.parse("#{date} #{t}#{time_offset}").utc
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
history_params[:date] = DateTime.parse(date).strftime("%Y/%m/%d %w")
|
||||||
|
history_params[:created_at] = DateTime.parse(date + " " + first_time + time_offset).utc
|
||||||
|
history_params
|
||||||
|
end
|
||||||
|
def task_params
|
||||||
|
task_params = params.require(:ruling_timer_task).permit!
|
||||||
|
task_params[:user_ids] = [] if task_params[:user_ids].nil?
|
||||||
|
task_params
|
||||||
|
end
|
||||||
|
def timer_params
|
||||||
|
if params[:type] == "temp"
|
||||||
|
params.require(:ruling_timer_temp).permit!
|
||||||
|
else
|
||||||
|
params.require(:ruling_timer_history).permit!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def set_weekdays
|
||||||
|
@weekdays = ["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]
|
||||||
|
@weekdays.map!{|d| I18n.t("ruling_timer.week_days.#{d}")}
|
||||||
|
end
|
||||||
|
class RulingPager
|
||||||
|
attr_accessor :count,:page,:per
|
||||||
|
def initialize(attrs)
|
||||||
|
if attrs.class == Hash
|
||||||
|
attrs.each do |k,v|
|
||||||
|
self.send("#{k}=",v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def current_page
|
||||||
|
self.page.to_i
|
||||||
|
end
|
||||||
|
def limit_value
|
||||||
|
self.per
|
||||||
|
end
|
||||||
|
def total_pages
|
||||||
|
(self.count / self.per).ceil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,137 @@
|
||||||
|
class RulingTimersController < ApplicationController
|
||||||
|
before_action :set_timer
|
||||||
|
def save_work_times
|
||||||
|
@ruling_timer_temp.update_work_times(params[:work_times])
|
||||||
|
@ruling_timer_temp.recalc_all
|
||||||
|
render :json => @ruling_timer_temp.to_json
|
||||||
|
end
|
||||||
|
def get_work_times
|
||||||
|
render :json => @ruling_timer_temp.get_work_times(true)
|
||||||
|
end
|
||||||
|
def start
|
||||||
|
render :json => @ruling_timer_temp.start(params[:timer_offset])
|
||||||
|
end
|
||||||
|
def stop
|
||||||
|
render :json => @ruling_timer_temp.stop
|
||||||
|
end
|
||||||
|
def rest
|
||||||
|
render :json => @ruling_timer_temp.rest
|
||||||
|
end
|
||||||
|
def set_summary
|
||||||
|
begin
|
||||||
|
@ruling_timer_temp.summary = params[:summary]
|
||||||
|
@ruling_timer_temp.save!
|
||||||
|
render :json => {"success"=>true}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def add_task
|
||||||
|
begin
|
||||||
|
@ruling_timer_temp.tasks << params[:task]
|
||||||
|
task = RulingTimerTask.create(:task_name=>params[:task],:user_ids=>[current_user.id.to_s])
|
||||||
|
sub_task = task.ruling_timer_sub_tasks.first
|
||||||
|
@ruling_timer_temp.sub_task_ids << sub_task.id.to_s
|
||||||
|
@ruling_timer_temp.save!
|
||||||
|
if @ruling_timer_temp.status == "working"
|
||||||
|
sub_task.start(@ruling_timer_temp.time_offset)
|
||||||
|
else
|
||||||
|
sub_task.update(:time_offset =>@ruling_timer_temp.time_offset)
|
||||||
|
end
|
||||||
|
render :json => {"success"=>true,"task-id"=>sub_task.id,"task"=>sub_task.task_name}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def add_task_from_list
|
||||||
|
begin
|
||||||
|
task = RulingTimerTask.find(params[:task_id])
|
||||||
|
if !(task.user_ids.include?(current_user.id.to_s))
|
||||||
|
task.user_ids << current_user.id.to_s
|
||||||
|
task.save
|
||||||
|
end
|
||||||
|
sub_task = task.ruling_timer_sub_tasks.where(:user=>current_user).first
|
||||||
|
if !(@ruling_timer_temp.sub_task_ids.include?(sub_task.id.to_s))
|
||||||
|
@ruling_timer_temp.sub_task_ids << sub_task.id.to_s
|
||||||
|
@ruling_timer_temp.tasks << task.task_name
|
||||||
|
@ruling_timer_temp.save!
|
||||||
|
end
|
||||||
|
if @ruling_timer_temp.status == "working"
|
||||||
|
sub_task.start(@ruling_timer_temp.time_offset)
|
||||||
|
else
|
||||||
|
sub_task.update(:time_offset =>@ruling_timer_temp.time_offset)
|
||||||
|
end
|
||||||
|
render :json => {"success"=>true,"task-id"=>sub_task.id,"task"=>sub_task.task_name}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def remove_task
|
||||||
|
begin
|
||||||
|
@ruling_timer_temp.remove_task(params[:task_id])
|
||||||
|
render :json => {"success"=>true}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def start_task
|
||||||
|
task = RulingTimerSubTask.find(params[:task_id])
|
||||||
|
render :json => task.start
|
||||||
|
end
|
||||||
|
def stop_task
|
||||||
|
render :json => task.stop
|
||||||
|
end
|
||||||
|
def set_task_status
|
||||||
|
begin
|
||||||
|
idx = @ruling_timer_temp.sub_task_ids.index(params[:task_id])
|
||||||
|
if idx
|
||||||
|
task = RulingTimerSubTask.find(params[:task_id])
|
||||||
|
if params[:status].to_i == 0
|
||||||
|
@ruling_timer_temp.tasks_finished.delete(idx)
|
||||||
|
task.start
|
||||||
|
else
|
||||||
|
@ruling_timer_temp.tasks_finished << idx
|
||||||
|
@ruling_timer_temp.tasks_finished.uniq!
|
||||||
|
task.finish
|
||||||
|
end
|
||||||
|
@ruling_timer_temp.save!
|
||||||
|
end
|
||||||
|
render :json => {"success"=>true}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def add_event
|
||||||
|
begin
|
||||||
|
@ruling_timer_temp.add_event(*params[:event])
|
||||||
|
render :json => {"success"=>true}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def delete_event
|
||||||
|
begin
|
||||||
|
@ruling_timer_temp.delete_event(params[:event_idx].to_i)
|
||||||
|
render :json => {"success"=>true}
|
||||||
|
rescue => e
|
||||||
|
puts [e,e.backtrace]
|
||||||
|
render :json => {"success"=>false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def set_timer
|
||||||
|
@ruling_timer_temp = nil
|
||||||
|
user = (current_user rescue nil)
|
||||||
|
if (user rescue false)
|
||||||
|
@ruling_timer_temp = RulingTimerTemp.where(:user=>user).first
|
||||||
|
if @ruling_timer_temp.nil?
|
||||||
|
@ruling_timer_temp = RulingTimerTemp.create(:user=>user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
module Admin::RulingTimersHelper
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
module RulingTimersHelper
|
||||||
|
end
|
|
@ -0,0 +1,170 @@
|
||||||
|
class RulingTimerHistory
|
||||||
|
include Mongoid::Document
|
||||||
|
include Mongoid::Timestamps
|
||||||
|
field :work_time_str, type: String, default: "00:00:00"
|
||||||
|
field :rest_time_str, type: String, default: "00:00:00"
|
||||||
|
field :all_work_times_seconds, type: Integer, default: 0
|
||||||
|
field :all_rest_times_seconds, type: Integer, default: 0
|
||||||
|
field :work_times, type: Array, default: []
|
||||||
|
field :rest_times, type: Array, default: []
|
||||||
|
field :sub_task_ids, type: Array, default: [] #store RulingTimerSubTask id
|
||||||
|
field :tasks, type: Array, default: [] #store name only
|
||||||
|
field :tasks_finished, type: Array, default: [] #store index only
|
||||||
|
field :summary, type: String, default: ""
|
||||||
|
field :time_offset, type: String, default: "+8"
|
||||||
|
field :events, type: Array, default: []
|
||||||
|
field :date, type: String, default: ""
|
||||||
|
belongs_to :user
|
||||||
|
def fix_work_times(store=true)
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
work_times_range = self.work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
work_times_range = eliminate_intersection(work_times_range)
|
||||||
|
new_work_times_range = merge_ranges(work_times_range.uniq).flat_map{|range| [range.first,range.last]}
|
||||||
|
new_work_times_range = new_work_times_range[0...-1] if new_work_times_range.last == time_now
|
||||||
|
self.work_times = new_work_times_range
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save if store
|
||||||
|
self.work_times
|
||||||
|
end
|
||||||
|
def merge_work_times(new_work_times,store=true)
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
work_times_range = self.work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
new_work_times_range = new_work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
work_times_range = eliminate_intersection(work_times_range)
|
||||||
|
new_work_times_range = eliminate_intersection(new_work_times_range)
|
||||||
|
new_work_times_range = work_times_range + new_work_times_range
|
||||||
|
new_work_times_range = eliminate_intersection(new_work_times_range).sort_by{|range| range.first.to_i}
|
||||||
|
new_work_times_range = merge_ranges(new_work_times_range.uniq).flat_map{|range| [range.first,range.last]}
|
||||||
|
new_work_times_range = new_work_times_range[0...-1] if new_work_times_range.last == time_now
|
||||||
|
self.work_times = new_work_times_range
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save if store
|
||||||
|
self.work_times
|
||||||
|
end
|
||||||
|
def merge_ranges(ranges)
|
||||||
|
(0...(ranges.count)).each do |i|
|
||||||
|
next if ranges[i+1].nil?
|
||||||
|
if ranges[i].last == ranges[i+1].first
|
||||||
|
ranges[i+1] = (ranges[i].first .. ranges[i+1].last)
|
||||||
|
ranges[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ranges.compact
|
||||||
|
end
|
||||||
|
def eliminate_intersection(times_range)
|
||||||
|
times_range = times_range.sort_by{|range| range.first.to_i}
|
||||||
|
times_range.each_with_index do |range,i|
|
||||||
|
next if range.nil?
|
||||||
|
intersects = times_range[i+1..-1].map{|r| range & r}
|
||||||
|
if intersects.compact.count != 0
|
||||||
|
intersects.each_with_index do |intersect,j|
|
||||||
|
next if intersect.nil?
|
||||||
|
if range.last > intersect.last #overlap all
|
||||||
|
times_range[i+1+j] = nil
|
||||||
|
else
|
||||||
|
if range.first == intersect.first
|
||||||
|
times_range[i] = nil
|
||||||
|
else
|
||||||
|
times_range[i] = (range.first..intersect.first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
times_range.compact
|
||||||
|
end
|
||||||
|
def update_task_name(task_id,new_name)
|
||||||
|
idx = self.sub_task_ids.index(task_id)
|
||||||
|
self.tasks[idx] = new_name
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
def remove_task(task_id)
|
||||||
|
sub_task = RulingTimerSubTask.find(task_id) rescue nil
|
||||||
|
idx = self.sub_task_ids.index(task_id)
|
||||||
|
self.sub_task_ids.delete(task_id)
|
||||||
|
self.tasks.delete_at(idx)
|
||||||
|
self.tasks_finished = self.tasks_finished.to_a.map do |task_idx|
|
||||||
|
if task_idx == idx
|
||||||
|
nil
|
||||||
|
elsif task_idx > idx
|
||||||
|
task_idx - 1
|
||||||
|
else
|
||||||
|
task_idx
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
self.save!
|
||||||
|
sub_task.stop if sub_task
|
||||||
|
end
|
||||||
|
def get_work_times(display_seond=false)
|
||||||
|
work_times = self.work_times.clone
|
||||||
|
if display_seond
|
||||||
|
time_format = "%H:%M:%S"
|
||||||
|
else
|
||||||
|
time_format = "%H:%M"
|
||||||
|
end
|
||||||
|
work_times.map!{|t| convert_datetime(t).new_offset(self.time_offset).strftime(time_format)}
|
||||||
|
end
|
||||||
|
def convert_datetime(time)
|
||||||
|
if time.class == Time
|
||||||
|
return time.to_datetime
|
||||||
|
elsif time.class == DateTime
|
||||||
|
return time
|
||||||
|
elsif time.class == String
|
||||||
|
return DateTime.parse(time)
|
||||||
|
else
|
||||||
|
return Time.at(time).to_datetime #time is seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def get_infos
|
||||||
|
return {"work" => self.work_time_str, "rest" => self.rest_time_str}
|
||||||
|
end
|
||||||
|
def calc(field_name,padding=true,recalc=false)
|
||||||
|
time_infos = self.send(field_name).clone rescue []
|
||||||
|
record_field_name = "all_#{field_name}_seconds"
|
||||||
|
tmp_seconds = self.send(record_field_name)
|
||||||
|
all_seconds = 0
|
||||||
|
start_index = 0
|
||||||
|
if !recalc
|
||||||
|
all_seconds = tmp_seconds
|
||||||
|
start_index = time_infos.count - 1
|
||||||
|
start_index -= (start_index % 2)
|
||||||
|
time_infos = time_infos[start_index..-1].to_a
|
||||||
|
end
|
||||||
|
time_infos.push(DateTime.now.utc) if (padding && time_infos.count % 2 == 1)
|
||||||
|
time_infos.each_with_index do |t,i|
|
||||||
|
if i % 2 == 0
|
||||||
|
next_t = time_infos[i+1]
|
||||||
|
if !next_t.nil?
|
||||||
|
all_seconds += ((convert_datetime(next_t) - convert_datetime(t)) * 1.day).round
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.send("#{record_field_name}=",all_seconds)
|
||||||
|
return all_seconds
|
||||||
|
end
|
||||||
|
def recalc_all
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.calc("rest_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
self.save
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def getPaddedComp(comp)
|
||||||
|
return ((comp.to_i < 10) ? ('0' + comp.to_s) : comp.to_s)
|
||||||
|
end
|
||||||
|
def transform_second_to_time(seconds)
|
||||||
|
seconds = 0 if seconds.nil?
|
||||||
|
hour = 3600
|
||||||
|
minute = 60
|
||||||
|
total_hour = getPaddedComp(seconds / hour)
|
||||||
|
rest_seconds = seconds % hour
|
||||||
|
total_minute = getPaddedComp(rest_seconds / minute)
|
||||||
|
total_second = getPaddedComp(rest_seconds % minute)
|
||||||
|
return (total_hour + ":" + total_minute + ":" + total_second)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,146 @@
|
||||||
|
class RulingTimerSubTask
|
||||||
|
include Mongoid::Document
|
||||||
|
include Mongoid::Timestamps
|
||||||
|
field :task_name, type: String, default: ""
|
||||||
|
field :work_time_str, type: String, default: "00:00:00"
|
||||||
|
field :all_work_times_seconds, type: Integer, default: 0
|
||||||
|
field :work_times, type: Array, default: []
|
||||||
|
field :status, type: String, default: "stop" # stop, working, finish
|
||||||
|
field :time_offset, type: String, default: "+8"
|
||||||
|
belongs_to :ruling_timer_task
|
||||||
|
belongs_to :user
|
||||||
|
after_destroy do |record|
|
||||||
|
id = record.id.to_s
|
||||||
|
timers = RulingTimerTemp.where(:sub_task_ids=>id).to_a + RulingTimerHistory.where(:sub_task_ids=>id).to_a
|
||||||
|
timers.each do |timer|
|
||||||
|
timer.remove_task(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
after_save do |record|
|
||||||
|
if record.task_name_changed?
|
||||||
|
id = record.id.to_s
|
||||||
|
timers = RulingTimerTemp.where(:sub_task_ids=>id).to_a + RulingTimerHistory.where(:sub_task_ids=>id).to_a
|
||||||
|
timers.each do |timer|
|
||||||
|
timer.update_task_name(id,record.task_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def get_work_times(display_seond=false)
|
||||||
|
work_times = self.work_times.clone
|
||||||
|
if display_seond
|
||||||
|
time_format = "%Y/%m/%d %H:%M:%S"
|
||||||
|
else
|
||||||
|
time_format = "%Y/%m/%d %H:%M"
|
||||||
|
end
|
||||||
|
work_times.map!{|t| convert_datetime(t).new_offset(self.time_offset).strftime(time_format)}
|
||||||
|
end
|
||||||
|
def start(offset="+8")
|
||||||
|
self.time_offset = offset
|
||||||
|
self.status = "working"
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if self.work_times.count % 2 == 0
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.calc("work_times",false)
|
||||||
|
self.save
|
||||||
|
self.ruling_timer_task.update(:is_finished => false)
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def reset_all(except_fields=[])
|
||||||
|
unless self.new_record?
|
||||||
|
change_fields = self.changes.except(*(except_fields.map{|f| f.to_s}))
|
||||||
|
change_fields.each do |k,v|
|
||||||
|
self.send("#{k}=",v[0])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def stop
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if self.work_times.count % 2 == 1
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.status = "stop"
|
||||||
|
self.calc("work_times",false)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def finish
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if self.work_times.count % 2 == 1
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.status = "finish"
|
||||||
|
self.calc("work_times")
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save
|
||||||
|
self.ruling_timer_task.update(:is_finished => true)
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def recalc_all
|
||||||
|
self.calc("work_times",true,true)
|
||||||
|
self.save
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def to_json
|
||||||
|
return {"work" => self.all_work_times_seconds}
|
||||||
|
end
|
||||||
|
def get_infos
|
||||||
|
self.calc("work_times")
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.reset_all([:work_time_str])
|
||||||
|
return {"work" => self.work_time_str}
|
||||||
|
end
|
||||||
|
def convert_datetime(time)
|
||||||
|
if time.class == Time
|
||||||
|
return time.to_datetime
|
||||||
|
elsif time.class == DateTime
|
||||||
|
return time
|
||||||
|
elsif time.class == String
|
||||||
|
return DateTime.parse(time)
|
||||||
|
else
|
||||||
|
return Time.at(time).to_datetime #time is seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def getPaddedComp(comp)
|
||||||
|
return ((comp.to_i < 10) ? ('0' + comp.to_s) : comp.to_s)
|
||||||
|
end
|
||||||
|
def transform_second_to_time(seconds)
|
||||||
|
hour = 3600
|
||||||
|
minute = 60
|
||||||
|
total_hour = getPaddedComp(seconds / hour)
|
||||||
|
rest_seconds = seconds % hour
|
||||||
|
total_minute = getPaddedComp(rest_seconds / minute)
|
||||||
|
total_second = getPaddedComp(rest_seconds % minute)
|
||||||
|
return (total_hour + ":" + total_minute + ":" + total_second)
|
||||||
|
end
|
||||||
|
def calc(field_name,padding=true,recalc=false)
|
||||||
|
time_infos = self.send(field_name).clone rescue []
|
||||||
|
record_field_name = "all_#{field_name}_seconds"
|
||||||
|
tmp_seconds = self.send(record_field_name)
|
||||||
|
all_seconds = 0
|
||||||
|
start_index = 0
|
||||||
|
if !recalc
|
||||||
|
all_seconds = tmp_seconds
|
||||||
|
start_index = time_infos.count - 1
|
||||||
|
start_index -= (start_index % 2)
|
||||||
|
time_infos = time_infos[start_index..-1].to_a
|
||||||
|
if !(self.send("#{field_name}_changed?")) && time_infos.count != 1
|
||||||
|
return all_seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
time_infos.push(DateTime.now.utc) if (padding && time_infos.count % 2 == 1)
|
||||||
|
time_infos.each_with_index do |t,i|
|
||||||
|
if i % 2 == 0
|
||||||
|
next_t = time_infos[i+1]
|
||||||
|
if !next_t.nil?
|
||||||
|
all_seconds += ((convert_datetime(next_t) - convert_datetime(t)) * 1.day).round
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.send("#{record_field_name}=",all_seconds)
|
||||||
|
return all_seconds
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
class RulingTimerTask
|
||||||
|
include Mongoid::Document
|
||||||
|
include Mongoid::Timestamps
|
||||||
|
field :task_name, type: String, default: ""
|
||||||
|
has_many :ruling_timer_sub_tasks, :autosave => true, :dependent => :destroy
|
||||||
|
field :user_ids, type: Array, default: []
|
||||||
|
field :is_finished, type: Boolean, default: false
|
||||||
|
def users
|
||||||
|
User.where(:id.in=>self.user_ids)
|
||||||
|
end
|
||||||
|
after_save do |record|
|
||||||
|
if record.user_ids_changed?
|
||||||
|
delete_user_ids = record.user_ids_was.to_a - record.user_ids.to_a
|
||||||
|
add_user_ids = record.user_ids.to_a - record.user_ids_was.to_a
|
||||||
|
delete_user_ids.each do |user|
|
||||||
|
self.ruling_timer_sub_tasks.where(:user=>user).destroy
|
||||||
|
end
|
||||||
|
add_user_ids.each do |user|
|
||||||
|
self.ruling_timer_sub_tasks.create(:user=>user,:task_name=>task_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if record.task_name_changed?
|
||||||
|
self.ruling_timer_sub_tasks.each do |sub_task|
|
||||||
|
sub_task.update(:task_name=>task_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,358 @@
|
||||||
|
class RulingTimerTemp
|
||||||
|
include Mongoid::Document
|
||||||
|
include Mongoid::Timestamps
|
||||||
|
field :status, type: String, default: "stop" # working, rest, stop
|
||||||
|
field :work_time_str, type: String, default: "00:00:00"
|
||||||
|
field :rest_time_str, type: String, default: "00:00:00"
|
||||||
|
field :all_work_times_seconds, type: Integer, default: 0
|
||||||
|
field :all_rest_times_seconds, type: Integer, default: 0
|
||||||
|
field :work_times, type: Array, default: []
|
||||||
|
field :rest_times, type: Array, default: []
|
||||||
|
field :sub_task_ids, type: Array, default: [] #store RulingTimerSubTask id
|
||||||
|
field :tasks, type: Array, default: [] #store name only
|
||||||
|
field :tasks_finished, type: Array, default: [] #store index only
|
||||||
|
field :summary, type: String, default: ""
|
||||||
|
field :time_offset, type: String, default: "+8"
|
||||||
|
field :events, type: Array, default: []
|
||||||
|
field :date, type: String, default: ""
|
||||||
|
belongs_to :user
|
||||||
|
before_create do |record|
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
record.date = time_now.new_offset(self.time_offset).strftime("%Y/%m/%d %w") if record.date.blank?
|
||||||
|
end
|
||||||
|
def update_work_times(work_times,store=true)
|
||||||
|
work_times = work_times.to_a
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
self.date = time_now.new_offset(self.time_offset).strftime("%Y/%m/%d %w") if self.date.blank?
|
||||||
|
date_str = self.date.split(" ")[0]
|
||||||
|
work_times = work_times.to_a.map do |t|
|
||||||
|
if t.blank?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
DateTime.parse("#{date_str} #{t}#{self.time_offset}").utc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.work_times = work_times
|
||||||
|
self.save if store
|
||||||
|
self.work_times
|
||||||
|
end
|
||||||
|
def fix_work_times(store=true)
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
work_times_range = self.work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
work_times_range = eliminate_intersection(work_times_range)
|
||||||
|
new_work_times_range = merge_ranges(work_times_range.uniq).flat_map{|range| [range.first,range.last]}
|
||||||
|
new_work_times_range = new_work_times_range[0...-1] if new_work_times_range.last == time_now
|
||||||
|
self.work_times = new_work_times_range
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save if store
|
||||||
|
self.work_times
|
||||||
|
end
|
||||||
|
def merge_work_times(new_work_times,store=true)
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
work_times_range = self.work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
new_work_times_range = new_work_times.each_slice(2).to_a.map{|a,b| convert_datetime(a)..(b.nil? ? time_now : convert_datetime(b))}
|
||||||
|
work_times_range = eliminate_intersection(work_times_range)
|
||||||
|
new_work_times_range = eliminate_intersection(new_work_times_range)
|
||||||
|
new_work_times_range = work_times_range + new_work_times_range
|
||||||
|
new_work_times_range = eliminate_intersection(new_work_times_range).sort_by{|range| range.first.to_i}
|
||||||
|
new_work_times_range = merge_ranges(new_work_times_range.uniq).flat_map{|range| [range.first,range.last]}
|
||||||
|
new_work_times_range = new_work_times_range[0...-1] if new_work_times_range.last == time_now
|
||||||
|
self.work_times = new_work_times_range
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.save if store
|
||||||
|
self.work_times
|
||||||
|
end
|
||||||
|
def merge_ranges(ranges)
|
||||||
|
(0...(ranges.count)).each do |i|
|
||||||
|
next if ranges[i+1].nil?
|
||||||
|
if ranges[i].last == ranges[i+1].first
|
||||||
|
ranges[i+1] = (ranges[i].first .. ranges[i+1].last)
|
||||||
|
ranges[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ranges.compact
|
||||||
|
end
|
||||||
|
def eliminate_intersection(times_range)
|
||||||
|
times_range = times_range.sort_by{|range| range.first.to_i}
|
||||||
|
times_range.each_with_index do |range,i|
|
||||||
|
next if range.nil?
|
||||||
|
intersects = times_range[i+1..-1].map{|r| range & r}
|
||||||
|
if intersects.compact.count != 0
|
||||||
|
intersects.each_with_index do |intersect,j|
|
||||||
|
next if intersect.nil?
|
||||||
|
if range.last > intersect.last #overlap all
|
||||||
|
times_range[i+1+j] = nil
|
||||||
|
else
|
||||||
|
if range.first == intersect.first
|
||||||
|
times_range[i] = nil
|
||||||
|
else
|
||||||
|
times_range[i] = (range.first..intersect.first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
times_range.compact
|
||||||
|
end
|
||||||
|
def update_task_name(task_id,new_name)
|
||||||
|
idx = self.sub_task_ids.index(task_id)
|
||||||
|
self.tasks[idx] = new_name
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
def remove_task(task_id)
|
||||||
|
sub_task = RulingTimerSubTask.find(task_id) rescue nil
|
||||||
|
idx = self.sub_task_ids.index(task_id)
|
||||||
|
self.sub_task_ids.delete(task_id)
|
||||||
|
self.tasks.delete_at(idx)
|
||||||
|
self.tasks_finished = self.tasks_finished.to_a.map do |task_idx|
|
||||||
|
if task_idx == idx
|
||||||
|
nil
|
||||||
|
elsif task_idx > idx
|
||||||
|
task_idx - 1
|
||||||
|
else
|
||||||
|
task_idx
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
self.save!
|
||||||
|
sub_task.stop if sub_task
|
||||||
|
end
|
||||||
|
def get_work_times(display_seond=false)
|
||||||
|
work_times = self.work_times.clone
|
||||||
|
if display_seond
|
||||||
|
time_format = "%H:%M:%S"
|
||||||
|
else
|
||||||
|
time_format = "%H:%M"
|
||||||
|
end
|
||||||
|
work_times.map!{|t| convert_datetime(t).new_offset(self.time_offset).strftime(time_format)}
|
||||||
|
end
|
||||||
|
def get_last_work_time
|
||||||
|
last_work_time = self.work_times.last
|
||||||
|
if last_work_time.nil?
|
||||||
|
last_work_time = DateTime.now.utc.new_offset(self.time_offset)
|
||||||
|
else
|
||||||
|
last_work_time = convert_datetime(last_work_time).new_offset(self.time_offset)
|
||||||
|
end
|
||||||
|
return last_work_time
|
||||||
|
end
|
||||||
|
def check_and_store
|
||||||
|
unless self.new_record?
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if time_now.new_offset(self.time_offset).strftime('%Y/%m/%d') != self.get_last_work_time.strftime('%Y/%m/%d')
|
||||||
|
self.stop
|
||||||
|
self.store
|
||||||
|
end
|
||||||
|
date_str = time_now.new_offset(self.time_offset).strftime("%Y/%m/%d %w")
|
||||||
|
if self.date != date_str
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def start(offset=nil)
|
||||||
|
self.time_offset = offset if offset
|
||||||
|
self.check_and_store
|
||||||
|
self.status = "working"
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
self.date = time_now.new_offset(self.time_offset).strftime("%Y/%m/%d %w")
|
||||||
|
if self.work_times.count % 2 == 0
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
if self.rest_times.count % 2 == 1
|
||||||
|
self.rest_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.calc("work_times",false)
|
||||||
|
self.calc("rest_times",false)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
self.save
|
||||||
|
self.start_all_task
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def reset_all(except_fields=[])
|
||||||
|
unless self.new_record?
|
||||||
|
change_fields = self.changes.except(*(except_fields.map{|f| f.to_s}))
|
||||||
|
change_fields.each do |k,v|
|
||||||
|
self.send("#{k}=",v[0])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def start_all_task
|
||||||
|
temp_task_ids = self.sub_task_ids
|
||||||
|
if self.tasks_finished.count != 0
|
||||||
|
temp_task_ids = temp_task_ids.select.with_index{|id,i| !(self.tasks_finished.include?(i))}
|
||||||
|
end
|
||||||
|
temp_tasks = RulingTimerSubTask.where(:id.in=>temp_task_ids).to_a
|
||||||
|
temp_tasks.each{|task| task.start}
|
||||||
|
end
|
||||||
|
def stop_all_task
|
||||||
|
temp_task_ids = self.sub_task_ids
|
||||||
|
if self.tasks_finished.count != 0
|
||||||
|
temp_task_ids = temp_task_ids.select.with_index{|id,i| !(self.tasks_finished.include?(i))}
|
||||||
|
end
|
||||||
|
temp_tasks = RulingTimerSubTask.where(:id.in=>temp_task_ids).to_a
|
||||||
|
temp_tasks.each{|task| task.stop}
|
||||||
|
end
|
||||||
|
def stop
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if self.work_times.count % 2 == 1
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.status = "stop"
|
||||||
|
self.calc("work_times",false)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
self.save
|
||||||
|
self.stop_all_task
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def rest
|
||||||
|
self.status = "rest"
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
if self.work_times.count % 2 == 1
|
||||||
|
self.work_times.push(time_now)
|
||||||
|
end
|
||||||
|
if self.rest_times.count % 2 == 0
|
||||||
|
self.rest_times.push(time_now)
|
||||||
|
end
|
||||||
|
self.calc("work_times",false)
|
||||||
|
self.calc("rest_times",false)
|
||||||
|
self.save
|
||||||
|
self.stop_all_task
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def recalc_all
|
||||||
|
self.calc("work_times",false,true)
|
||||||
|
self.calc("rest_times",false,true)
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
self.save
|
||||||
|
return self.to_json
|
||||||
|
end
|
||||||
|
def to_json
|
||||||
|
self.calc("work_times")
|
||||||
|
self.calc("rest_times")
|
||||||
|
work_seconds = self.all_work_times_seconds
|
||||||
|
rest_seconds = self.all_rest_times_seconds
|
||||||
|
self.reset_all
|
||||||
|
return {"work" => work_seconds, "rest" => rest_seconds}
|
||||||
|
end
|
||||||
|
def get_infos
|
||||||
|
self.calc("work_times")
|
||||||
|
self.calc("rest_times")
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
self.reset_all([:work_time_str,:rest_time_str])
|
||||||
|
return {"work" => self.work_time_str, "rest" => self.rest_time_str}
|
||||||
|
end
|
||||||
|
def convert_datetime(time)
|
||||||
|
if time.class == Time
|
||||||
|
return time.to_datetime
|
||||||
|
elsif time.class == DateTime
|
||||||
|
return time
|
||||||
|
elsif time.class == String
|
||||||
|
return DateTime.parse(time)
|
||||||
|
else
|
||||||
|
return Time.at(time).to_datetime #time is seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def getPaddedComp(comp)
|
||||||
|
return ((comp.to_i < 10) ? ('0' + comp.to_s) : comp.to_s)
|
||||||
|
end
|
||||||
|
def transform_second_to_time(seconds)
|
||||||
|
seconds = 0 if seconds.nil?
|
||||||
|
hour = 3600
|
||||||
|
minute = 60
|
||||||
|
total_hour = getPaddedComp(seconds / hour)
|
||||||
|
rest_seconds = seconds % hour
|
||||||
|
total_minute = getPaddedComp(rest_seconds / minute)
|
||||||
|
total_second = getPaddedComp(rest_seconds % minute)
|
||||||
|
return (total_hour + ":" + total_minute + ":" + total_second)
|
||||||
|
end
|
||||||
|
def calc(field_name,padding=true,recalc=false)
|
||||||
|
time_infos = self.send(field_name).clone rescue []
|
||||||
|
record_field_name = "all_#{field_name}_seconds"
|
||||||
|
tmp_seconds = self.send(record_field_name)
|
||||||
|
all_seconds = 0
|
||||||
|
start_index = 0
|
||||||
|
if !recalc
|
||||||
|
all_seconds = tmp_seconds
|
||||||
|
start_index = time_infos.count - 1
|
||||||
|
start_index -= (start_index % 2)
|
||||||
|
time_infos = time_infos[start_index..-1].to_a
|
||||||
|
if !(self.send("#{field_name}_changed?")) && time_infos.count != 1
|
||||||
|
return all_seconds
|
||||||
|
end
|
||||||
|
end
|
||||||
|
time_infos.push(DateTime.now.utc) if (padding && time_infos.count % 2 == 1)
|
||||||
|
time_infos.each_with_index do |t,i|
|
||||||
|
if i % 2 == 0
|
||||||
|
next_t = time_infos[i+1]
|
||||||
|
if !next_t.nil?
|
||||||
|
all_seconds += ((convert_datetime(next_t) - convert_datetime(t)) * 1.day).round
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.send("#{record_field_name}=",all_seconds)
|
||||||
|
return all_seconds
|
||||||
|
end
|
||||||
|
def add_event(startt,endt,event)
|
||||||
|
self.events << {"start"=>startt,"end"=> endt,"event"=>event}
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
def delete_event(idx)
|
||||||
|
self.events.delete_at(idx)
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
def store
|
||||||
|
self.calc("work_times")
|
||||||
|
self.calc("rest_times")
|
||||||
|
self.work_time_str = transform_second_to_time(self.all_work_times_seconds)
|
||||||
|
self.rest_time_str = transform_second_to_time(self.all_rest_times_seconds)
|
||||||
|
fields = self.fields.except("_id","created_at","updated_at","status").keys
|
||||||
|
if self.all_work_times_seconds != 0 || self.all_rest_times_seconds != 0
|
||||||
|
ruling_timer_history = RulingTimerHistory.new
|
||||||
|
fields.each do |f|
|
||||||
|
field_name = f
|
||||||
|
if (self.fields[f].options[:localize] rescue false)
|
||||||
|
field_name = "#{f}_translations"
|
||||||
|
end
|
||||||
|
ruling_timer_history.send("#{field_name}=",self.send(field_name))
|
||||||
|
end
|
||||||
|
ruling_timer_history.date = self.get_last_work_time.new_offset(self.time_offset).strftime("%Y/%m/%d %w")
|
||||||
|
ruling_timer_history.user = self.user
|
||||||
|
ruling_timer_history.save
|
||||||
|
end
|
||||||
|
available_locales = I18n.available_locales
|
||||||
|
not_reset_fields = ["time_offset","user_id","tasks_finished","sub_task_ids","tasks"]
|
||||||
|
fields.each do |f|
|
||||||
|
next if not_reset_fields.include?(f)
|
||||||
|
default_value = self.fields[f].options[:default] rescue nil
|
||||||
|
if (self.fields[f].options[:localize] rescue false)
|
||||||
|
field_name = "#{f}_translations"
|
||||||
|
self.send("#{f}_translations=",available_locales.map{|l| [l.to_s,default_value]}.to_h)
|
||||||
|
else
|
||||||
|
self.send("#{f}=",default_value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if self.tasks_finished.count != 0
|
||||||
|
temp_task_ids = self.sub_task_ids.clone
|
||||||
|
temp_tasks = self.tasks.clone
|
||||||
|
temp_tasks_finished = self.tasks_finished.clone.sort.reverse
|
||||||
|
temp_tasks_finished.each do |idx|
|
||||||
|
temp_task_ids.delete_at(idx)
|
||||||
|
temp_tasks.delete_at(idx)
|
||||||
|
end
|
||||||
|
self.sub_task_ids = temp_task_ids
|
||||||
|
self.tasks = temp_tasks
|
||||||
|
self.tasks_finished = []
|
||||||
|
end
|
||||||
|
time_now = DateTime.now.utc
|
||||||
|
self.created_at = time_now
|
||||||
|
self.updated_at = time_now
|
||||||
|
self.date = time_now.new_offset(self.time_offset).strftime("%Y/%m/%d %w")
|
||||||
|
self.save
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted"><%= t("ruling_timer.task_name") %></label>
|
||||||
|
<div class="controls">
|
||||||
|
<%= f.text_field :task_name %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted"><%= t("ruling_timer.task_performer") %></label>
|
||||||
|
<div class="controls">
|
||||||
|
<div id="user_append_area" class="checkbox-card">
|
||||||
|
<%= @selected_users.join("\n").html_safe %>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
<button id="add_user_btn" type="button" class="btn btn-primary"><%=t("ruling_timer.add_member")%></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="form-actions">
|
||||||
|
<% referer = request.referer rescue nil %>
|
||||||
|
<% referer = get_referer_url if referer.blank? || request.host != URI.parse(URI.encode(referer)).host %>
|
||||||
|
<%= f.submit t('submit'), class: 'btn btn-primary' %>
|
||||||
|
<input type="hidden" name="referer_url" value="<%= referer %>">
|
||||||
|
<%= link_to t('cancel'), referer, :class=>"btn" %>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#add_user_btn").click(function(){
|
||||||
|
$("#user_modal").modal("show");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<tr>
|
||||||
|
<td><%= timer.date.split(" ")[0] + " " + @weekdays[timer.date.split(" ")[1].to_i] %></td>
|
||||||
|
<td><%= timer.summary.to_s.gsub("\n","<br>").html_safe %></td>
|
||||||
|
<td>
|
||||||
|
<% work_times = timer.get_work_times %>
|
||||||
|
<% work_times.each_with_index do |t,i| %>
|
||||||
|
<% if i % 2 == 0
|
||||||
|
next_t = work_times[i+1]
|
||||||
|
else
|
||||||
|
next
|
||||||
|
end %>
|
||||||
|
<%= "#{t} ~ #{next_t.nil? ? "Now" : next_t}" %>
|
||||||
|
<br>
|
||||||
|
<% end %>
|
||||||
|
<%= I18n.t("ruling_timer.total") %>: <%= timer.get_infos["work"] %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="<%=(timer.class == RulingTimerTemp ? edit_temp_timer_admin_ruling_timer_path(:id=>timer.id) : edit_timer_admin_ruling_timer_path(:id=>timer.id))%>" class="btn btn-primary"><%=t("edit")%></a>
|
||||||
|
<% if params[:id].nil? || params[:id] == current_user.id.to_s%>
|
||||||
|
<a data-id="<%=timer.id.to_s%>" data-type="<%= 'temp' if timer.class == RulingTimerTemp%>" data-history-date="<%=timer.date.to_s.split(' ')[0]%>" class="btn btn-danger delete_history"><%=t("delete_")%></a>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<% checked = false if defined?(checked).nil? %>
|
||||||
|
<li class="check-item <%=checked ? 'active' : ''%>">
|
||||||
|
<label>
|
||||||
|
<%= image_tag ((user.member_profile.avatar.file rescue false) ? user.member_profile.avatar : "member-pic.png"), class: "user-pic" %>
|
||||||
|
<span class="user-name"><%= user.member_profile.name rescue 'noname' %></span>
|
||||||
|
</label>
|
||||||
|
<%= check_box_tag "#{@user_params_name}", (user.id rescue nil) , (checked ? true : false) , :id => "user_ids_#{(user.id rescue '')}"%>
|
||||||
|
</li>
|
|
@ -0,0 +1,61 @@
|
||||||
|
<%= stylesheet_link_tag "member_select" %>
|
||||||
|
<%= stylesheet_link_tag "lib/checkbox-card" %>
|
||||||
|
<style>
|
||||||
|
.checkbox-card li .member-pic {
|
||||||
|
float: left;
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="modal hide fade" tabindex="-1" role="dialog" id="user_modal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><%=t("ruling_timer.member")%></h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="member-filter-result nano">
|
||||||
|
<div>
|
||||||
|
<ul class="checkbox-card clearfix">
|
||||||
|
<% @selected_user_ids = @selected_user_ids.to_a %>
|
||||||
|
<% @selected_users = [] %>
|
||||||
|
<% @users.each do |user| %>
|
||||||
|
<% if @selected_user_ids.include?(user.id.to_s) %>
|
||||||
|
<% @selected_users << render(:partial => "user_card",:locals=>{:user=>user,:checked=>true}) %>
|
||||||
|
<% else %>
|
||||||
|
<%= render :partial => "user_card",:locals=>{:user=>user} %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary save_button">Save changes</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
function set_modal_max_height(){
|
||||||
|
var modal_body = $(this).find(".modal-body");
|
||||||
|
modal_body.css("max-height","auto");
|
||||||
|
var window_height = $(window).height();
|
||||||
|
var max_height = window_height - $(".modal-header").height() - $(".modal-footer").height() - 20 - window_height * 0.2 - 15 * 2;
|
||||||
|
modal_body.css("max-height",max_height);
|
||||||
|
}
|
||||||
|
$(".modal").on("shown.bs.modal",set_modal_max_height);
|
||||||
|
$(window).resize(function(){
|
||||||
|
set_modal_max_height.call($(".modal"));
|
||||||
|
})
|
||||||
|
$(".save_button").off("click").on("click",function(){
|
||||||
|
var modal = $(this).parents(".modal");
|
||||||
|
var li_selected = modal.find(".check-item input:checked").parents("li");
|
||||||
|
$("<%=@target%>").append(li_selected);
|
||||||
|
modal.modal('hide');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
|
@ -0,0 +1,78 @@
|
||||||
|
<style type="text/css">
|
||||||
|
.timer_block input{
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
.remove_btn{
|
||||||
|
color: red;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
.remove_btn:hover{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h3><%=@user.name%></h3>
|
||||||
|
<%= form_for @history, url: create_history_admin_ruling_timers_path(@history), html: {class: "form-horizontal main-forms previewable"} do |f| %>
|
||||||
|
<fieldset>
|
||||||
|
<%= f.hidden_field :user_id%>
|
||||||
|
<%= f.hidden_field :time_offset, :id=>"history_timeoffset" %>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted" for="ruling_timer_history_date" ><%= t("ruling_timer.date") %></label>
|
||||||
|
<div class="controls">
|
||||||
|
<%= f.datetime_picker :date, {:picker_type=>"date",:no_label=>true,:format=>"yyyy/MM/dd"} %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted" for="edit_summary" ><%= t("ruling_timer.summary") %></label>
|
||||||
|
<div class="controls">
|
||||||
|
<%= f.text_area :summary,:id=>"edit_summary" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted"><%=t("ruling_timer.work_time")%></label>
|
||||||
|
<div class="controls timer_block">
|
||||||
|
<% work_times = @history.get_work_times(true) %>
|
||||||
|
<% work_times.each_with_index do |t,i| %>
|
||||||
|
<% if i % 2 == 0
|
||||||
|
next_t = work_times[i+1]
|
||||||
|
else
|
||||||
|
next
|
||||||
|
end %>
|
||||||
|
<span><button class="remove_btn" type="button">X</button><%= f.text_field_tag "#{f.object_name}[work_times][]", t ,:id=>nil %> ~ <%= f.text_field_tag "#{f.object_name}[work_times][]", next_t ,:id=>nil %></span>
|
||||||
|
<hr>
|
||||||
|
<% end %>
|
||||||
|
<div class="add_target"></div>
|
||||||
|
<button type="button" class="btn btn-primary" id="add_timer"><%=t(:add)%></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="form-actions">
|
||||||
|
<% referer = request.referer rescue nil %>
|
||||||
|
<% referer = get_referer_url if referer.blank? || request.host != URI.parse(URI.encode(referer)).host %>
|
||||||
|
<%= f.submit t('submit'), class: 'btn btn-primary' %>
|
||||||
|
<input type="hidden" name="referer_url" value="<%= referer %>">
|
||||||
|
<%= link_to t('cancel'), referer, :class=>"btn" %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<script>
|
||||||
|
window.timer_offset = (new Date().getTimezoneOffset() / -60).toString();
|
||||||
|
if(window.timer_offset[0] != "-"){
|
||||||
|
window.timer_offset = "+" + window.timer_offset;
|
||||||
|
}
|
||||||
|
$(document).ready(function(){
|
||||||
|
$("#history_timeoffset").val(window.timer_offset);
|
||||||
|
$('.timer_block input').ui_timepicker({timeFormat: 'H:mm:ss'});
|
||||||
|
$("#add_timer").click(function(){
|
||||||
|
var timer_html = $('<span><button class="remove_btn" type="button">X</button><%= f.text_field_tag "#{f.object_name}[work_times][]",nil,:id=>nil %> ~ <%= f.text_field_tag "#{f.object_name}[work_times][]",nil,:id=>nil %></span><hr>');
|
||||||
|
$(".add_target").append(timer_html);
|
||||||
|
timer_html.find("input").ui_timepicker({timeFormat: 'H:mm:ss'});
|
||||||
|
})
|
||||||
|
$(document).on("click",".remove_btn",function(){
|
||||||
|
var item = $(this).parent();
|
||||||
|
item.next("hr").remove();
|
||||||
|
item.remove();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<%= render :partial => "user_select_modal",:locals=>{:@users=>@users,:@selected_user_ids=>@task.user_ids,:@target=>"#user_append_area",:@user_params_name=>"#{@task.class.to_s.underscore}[user_ids][]"} %>
|
||||||
|
<%= form_for @task, url: create_task_admin_ruling_timers_path(@task), html: {class: "form-horizontal main-forms previewable"} do |f| %>
|
||||||
|
<fieldset>
|
||||||
|
<%= render :partial => "task_form",:locals=>{:f=>f} %>
|
||||||
|
</fieldset>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<%= render :partial => "user_select_modal",:locals=>{:@users=>@users,:@selected_user_ids=>@task.user_ids,:@target=>"#user_append_area",:@user_params_name=>"#{@task.class.to_s.underscore}[user_ids][]"} %>
|
||||||
|
<%= form_for @task, url: update_task_admin_ruling_timer_path(@task), html: {class: "form-horizontal main-forms previewable"} do |f| %>
|
||||||
|
<fieldset>
|
||||||
|
<%= render :partial => "task_form",:locals=>{:f=>f} %>
|
||||||
|
</fieldset>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<style type="text/css">
|
||||||
|
.timer_block input{
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
.remove_btn{
|
||||||
|
color: red;
|
||||||
|
margin-right: 0.3em;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
.remove_btn:hover{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<%= form_for @timer, url: update_timer_admin_ruling_timer_path(@timer), html: {class: "form-horizontal main-forms previewable"} do |f| %>
|
||||||
|
<fieldset>
|
||||||
|
<h3><%= @timer.date.split(" ")[0] + " " + @weekdays[@timer.date.split(" ")[1].to_i] %></h3>
|
||||||
|
<% if @timer.class == RulingTimerTemp %>
|
||||||
|
<input name="type" value="temp" type="hidden">
|
||||||
|
<% end %>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted" for="edit_summary" ><%= t("ruling_timer.summary") %></label>
|
||||||
|
<div class="controls">
|
||||||
|
<%= f.text_area :summary,:id=>"edit_summary" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label muted"><%=t("ruling_timer.work_time")%></label>
|
||||||
|
<div class="controls timer_block">
|
||||||
|
<% work_times = @timer.get_work_times(true) %>
|
||||||
|
<% work_times.each_with_index do |t,i| %>
|
||||||
|
<% if i % 2 == 0
|
||||||
|
next_t = work_times[i+1]
|
||||||
|
else
|
||||||
|
next
|
||||||
|
end %>
|
||||||
|
<span><button class="remove_btn" type="button">X</button><%= f.text_field_tag "#{f.object_name}[work_times][]", t ,:id=>nil %> ~ <%= f.text_field_tag "#{f.object_name}[work_times][]", next_t ,:id=>nil %></span>
|
||||||
|
<hr>
|
||||||
|
<% end %>
|
||||||
|
<div class="add_target"></div>
|
||||||
|
<button type="button" class="btn btn-primary" id="add_timer"><%=t(:add)%></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="form-actions">
|
||||||
|
<% referer = request.referer rescue nil %>
|
||||||
|
<% referer = get_referer_url if referer.blank? || request.host != URI.parse(URI.encode(referer)).host %>
|
||||||
|
<%= f.submit t('submit'), class: 'btn btn-primary' %>
|
||||||
|
<input type="hidden" name="referer_url" value="<%= referer %>">
|
||||||
|
<%= link_to t('cancel'), referer, :class=>"btn" %>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function(){
|
||||||
|
$('.timer_block input').ui_timepicker({timeFormat: 'H:mm:ss'});
|
||||||
|
$("#add_timer").click(function(){
|
||||||
|
var timer_html = $('<span><button class="remove_btn" type="button">X</button><%= f.text_field_tag "#{f.object_name}[work_times][]",nil,:id=>nil %> ~ <%= f.text_field_tag "#{f.object_name}[work_times][]",nil,:id=>nil %></span><hr>');
|
||||||
|
$(".add_target").append(timer_html);
|
||||||
|
timer_html.find("input").ui_timepicker({timeFormat: 'H:mm:ss'});
|
||||||
|
})
|
||||||
|
$(document).on("click",".remove_btn",function(){
|
||||||
|
var item = $(this).parent();
|
||||||
|
item.next("hr").remove();
|
||||||
|
item.remove();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,85 @@
|
||||||
|
<h3>
|
||||||
|
<% if @user_name %>
|
||||||
|
<%=t("ruling_timer.user_work_history",{:user=>@user_name})%>
|
||||||
|
<% else %>
|
||||||
|
<%=t("ruling_timer.my_work_history")%>
|
||||||
|
<% end %>
|
||||||
|
</h3>
|
||||||
|
<table class="table main-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%=t("ruling_timer.date")%></th>
|
||||||
|
<th><%=t("ruling_timer.summary")%></th>
|
||||||
|
<th><%=t("ruling_timer.work_time")%></th>
|
||||||
|
<th><%=t("action")%></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% if @ruling_timer_temp %>
|
||||||
|
<%= render :partial => "timer_work_row", :locals=>{:timer=>@ruling_timer_temp} %>
|
||||||
|
<% end %>
|
||||||
|
<% @ruling_timer_history.each do |timer_history| %>
|
||||||
|
<%= render :partial => "timer_work_row", :locals=>{:timer=>timer_history} %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<%=
|
||||||
|
content_tag :div, class: "bottomnav clearfix" do
|
||||||
|
content_tag(:div, paginate(@ruling_pager), class: "pagination pagination-centered") +
|
||||||
|
link_to(t("ruling_timer.add_history"),add_history_admin_ruling_timer_path(:id=>(params[:id].nil? ? "current_user" : params[:id])),:class=>"btn btn-primary pull-right")
|
||||||
|
end
|
||||||
|
%>
|
||||||
|
<script>
|
||||||
|
$('.delete_history').click(function(){
|
||||||
|
var item = $(this);
|
||||||
|
var item_row = $(this).parents("tr").eq(0);
|
||||||
|
var history_id = $(item).data("id");
|
||||||
|
var type = $(item).data("type");
|
||||||
|
if(window.confirm("<%=t("ruling_timer.delete_history_hint")%>")){
|
||||||
|
if(window.confirm("<%=t("ruling_timer.delete_history_hint")%>")){
|
||||||
|
if($("#dialog-confirm").length == 0){
|
||||||
|
$("#main-wrap").before("<div id='dialog-confirm' title='<%=t("ruling_timer.delete_task")%>'>"+
|
||||||
|
"<div style='clear:both;'></div><div id='info_texts'>"+'<%=t("ruling_timer.delete_history_hint1")%>'.replace('{{history}}',item.data('history-date'))+"</div>"+"<input id=\"confirm_input\"/ placeholder=\"<%= t('ruling_timer.please_input_confirm_delete') %>\" style=\"width: 17em;\">"+"<div id='msg_end' style='height:0px; overflow:hidden'></div>"+
|
||||||
|
"</div>");
|
||||||
|
}else{
|
||||||
|
$("#info_texts").html('<%=t("ruling_timer.delete_history_hint1")%>'.replace('{{history}}',item.data('history-date')));
|
||||||
|
$('#confirm_input').css('display','block');
|
||||||
|
$('#confirm_input').val('');
|
||||||
|
}
|
||||||
|
$( "#dialog-confirm" ).dialog({
|
||||||
|
resizable: true,
|
||||||
|
minHeight: 200,
|
||||||
|
maxHeight: 400,
|
||||||
|
modal: true,
|
||||||
|
width: '80%',
|
||||||
|
open: function(){
|
||||||
|
$('#confirm_input').blur();
|
||||||
|
},
|
||||||
|
close: function(){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
"<%= t('ruling_timer.confirm') %>": function(){
|
||||||
|
var _this = $(this);
|
||||||
|
if($('#confirm_input').val().match(/<%= t('ruling_timer.confirm_delete') %>/gi)){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$.post(decodeURIComponent("<%=delete_history_admin_ruling_timer_path(:id=>"{{history_id}}")%>").replace("{{history_id}}",history_id),{'confirm_delete': true,'type': type}).done(function(data){
|
||||||
|
item_row.remove();
|
||||||
|
_this.dialog( "close" );
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
alert("<%= t('client_management.please_input_confirm_delete').html_safe %>");
|
||||||
|
$('#confirm_input').focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"<%= t('ruling_timer.cancel') %>": function(){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,130 @@
|
||||||
|
<style type="text/css">
|
||||||
|
.span20-percent{
|
||||||
|
width: 20%;
|
||||||
|
float: left;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0;
|
||||||
|
border: 2px solid #333333;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.span20-percent img{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.status-tags > *{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.task_name{
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.working_label{
|
||||||
|
color: #5a5a5a;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.finish_label{
|
||||||
|
color: #3ec700;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.stop_label{
|
||||||
|
color: #cd1643;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h3><%= t("ruling_timer.task") %></h3>
|
||||||
|
<table class="table main-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%= t("ruling_timer.task_name") %></th>
|
||||||
|
<th><%= t("ruling_timer.task_performer") %></th>
|
||||||
|
<th><%=t("action")%></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @tasks.each_with_index do |task,i| %>
|
||||||
|
<tr>
|
||||||
|
<td class="task_name"><%=task.task_name%></td>
|
||||||
|
<td class="task_performer">
|
||||||
|
<% if task.user_ids %>
|
||||||
|
<% user_ids = task.user_ids
|
||||||
|
user_bson_ids = user_ids.map{|id| BSON::ObjectId(id)}
|
||||||
|
%>
|
||||||
|
<% users = User.find(user_ids).index_by(&:id).slice(*user_bson_ids).values %>
|
||||||
|
<% users.each do |user| %>
|
||||||
|
<% sub_task = task.ruling_timer_sub_tasks.where(:user=>user).first %>
|
||||||
|
<% klass = (sub_task.status == 'working' ? 'fa-play' : (sub_task.status == 'finish' ? 'fa-flag-checkered' : 'fa-pause') rescue 'fa-pause') %>
|
||||||
|
<span><%=user.name%>( <i class="fa <%=klass%> <%=sub_task.status%>_label"></i> <%=sub_task.get_infos["work"] rescue "00:00:00" %> )</span>
|
||||||
|
<br>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="<%=view_task_admin_ruling_timer_path(:id=>task.id)%>" class="btn btn-primary"><%=t("view")%></a>
|
||||||
|
<a href="<%=edit_task_admin_ruling_timer_path(:id=>task.id)%>" class="btn btn-primary"><%=t("edit")%></a>
|
||||||
|
<a href="#" class="btn btn-danger delete_task" data-task="<%=task.task_name%>" data-task-id="<%=task.id.to_s%>"><%=t("ruling_timer.delete_task")%></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<%=
|
||||||
|
content_tag :div, class: "bottomnav clearfix" do
|
||||||
|
content_tag(:div, paginate(@tasks), class: "pagination pagination-centered") +
|
||||||
|
content_tag(:div, link_to(t("ruling_timer.add_task"), add_task_admin_ruling_timers_path, :class=>"btn btn-primary"), class: "pull-right")
|
||||||
|
end %>
|
||||||
|
<script>
|
||||||
|
<% ["working","stop","finish"].each do |status| %>
|
||||||
|
$(".<%=status%>_label").attr("title",'<%=t("ruling_timer.#{status}")%>');
|
||||||
|
<% end %>
|
||||||
|
$('.delete_task').click(function(){
|
||||||
|
var item = $(this);
|
||||||
|
var item_row = $(this).parents("tr").eq(0);
|
||||||
|
var task_id = $(item).data("task-id")
|
||||||
|
if(window.confirm("<%=t("ruling_timer.delete_task_hint")%>")){
|
||||||
|
if(window.confirm("<%=t("ruling_timer.delete_task_hint")%>")){
|
||||||
|
if($("#dialog-confirm").length == 0){
|
||||||
|
$("#main-wrap").before("<div id='dialog-confirm' title='<%=t("ruling_timer.delete_task")%>'>"+
|
||||||
|
"<div style='clear:both;'></div><div id='info_texts'>"+'<%=t("ruling_timer.delete_task_hint1")%>'.replace('{{task}}',item.data('task'))+"</div>"+"<input id=\"confirm_input\"/ placeholder=\"<%= t('ruling_timer.please_input_confirm_delete') %>\" style=\"width: 17em;\">"+"<div id='msg_end' style='height:0px; overflow:hidden'></div>"+
|
||||||
|
"</div>");
|
||||||
|
}else{
|
||||||
|
$("#info_texts").html('<%=t("ruling_timer.delete_task_hint1")%>'.replace('{{task}}',item.data('task')));
|
||||||
|
$('#confirm_input').css('display','block');
|
||||||
|
$('#confirm_input').val('');
|
||||||
|
}
|
||||||
|
$( "#dialog-confirm" ).dialog({
|
||||||
|
resizable: true,
|
||||||
|
minHeight: 200,
|
||||||
|
maxHeight: 400,
|
||||||
|
modal: true,
|
||||||
|
width: '80%',
|
||||||
|
open: function(){
|
||||||
|
$('#confirm_input').blur();
|
||||||
|
},
|
||||||
|
close: function(){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
"<%= t('ruling_timer.confirm') %>": function(){
|
||||||
|
var _this = $(this);
|
||||||
|
if($('#confirm_input').val().match(/<%= t('ruling_timer.confirm_delete') %>/gi)){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$.post(decodeURIComponent("<%=delete_task_admin_ruling_timer_path(:id=>"{{task_id}}")%>").replace("{{task_id}}",task_id),{'confirm_delete': true}).done(function(data){
|
||||||
|
item_row.remove();
|
||||||
|
_this.dialog( "close" );
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
alert("<%= t('client_management.please_input_confirm_delete').html_safe %>");
|
||||||
|
$('#confirm_input').focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"<%= t('ruling_timer.cancel') %>": function(){
|
||||||
|
$('#confirm_input').css('display','none');
|
||||||
|
$( this ).dialog( "close" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<style type="text/css">
|
||||||
|
.span20-percent{
|
||||||
|
width: 20%;
|
||||||
|
float: left;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0;
|
||||||
|
border: 2px solid #333333;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.span20-percent img{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.status-tags > *{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.working_label{
|
||||||
|
color: #3ec700;
|
||||||
|
}
|
||||||
|
.rest_label{
|
||||||
|
color: #5a5a5a;
|
||||||
|
}
|
||||||
|
.offline_label{
|
||||||
|
color: #cd1643;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h3><%= t("ruling_timer.member") %></h3>
|
||||||
|
<% timer_temps = @timer_temps.to_a%>
|
||||||
|
<% @active_users.each_with_index do |user,i| %>
|
||||||
|
<div class="span20-percent">
|
||||||
|
<% name = user.name %>
|
||||||
|
<% link = work_history_admin_ruling_timer_path(:id=>user.id) %>
|
||||||
|
<a href="<%=link%>" title="<%=name%>"><img src="<%=user.member_profile.avatar.url%>"></a>
|
||||||
|
<div class="status-tags">
|
||||||
|
<% timer_temp = timer_temps[i] %>
|
||||||
|
<% if timer_temp.status != "stop" %>
|
||||||
|
<span class="<%=timer_temp.status%>_label"><%=t("ruling_timer.#{timer_temp.status}")%></span>
|
||||||
|
<% else %>
|
||||||
|
<span class="offline_label"><%=t("ruling_timer.offline")%></span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<a href="<%=link%>" title="<%=name%>"><%=name%></a>
|
||||||
|
</div>
|
||||||
|
<%= '<div style=\"clear: both;\"></div>'.html_safe if (i % 5 == 4) %>
|
||||||
|
<% end %>
|
||||||
|
<%= paginate(@timer_temps)%>
|
|
@ -0,0 +1,66 @@
|
||||||
|
<style type="text/css">
|
||||||
|
.working_label{
|
||||||
|
color: #5a5a5a;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.finish_label{
|
||||||
|
color: #3ec700;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.stop_label{
|
||||||
|
color: #cd1643;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<fieldset>
|
||||||
|
<legend><h3><%= @task.task_name%></h3></legend>
|
||||||
|
<div class="field_body">
|
||||||
|
<table class="table main-list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><%= t("ruling_timer.task_performer") %></th>
|
||||||
|
<th><%= t("ruling_timer.task_execution_time") %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% if @task.user_ids %>
|
||||||
|
<% user_ids = @task.user_ids
|
||||||
|
user_bson_ids = user_ids.map{|id| BSON::ObjectId(id)}
|
||||||
|
%>
|
||||||
|
<% users = User.find(user_ids).index_by(&:id).slice(*user_bson_ids).values %>
|
||||||
|
<% users.each do |user| %>
|
||||||
|
<% sub_task = @task.ruling_timer_sub_tasks.where(:user=>user).first %>
|
||||||
|
<% klass = (sub_task.status == 'working' ? 'fa-play' : (sub_task.status == 'finish' ? 'fa-flag-checkered' : 'fa-pause') rescue 'fa-pause') %>
|
||||||
|
<tr>
|
||||||
|
<td><%=user.name%></td>
|
||||||
|
<td>
|
||||||
|
<% work_times = sub_task.get_work_times %>
|
||||||
|
<% work_times.each_with_index do |work_time,i| %>
|
||||||
|
<% next if i % 2 == 1 %>
|
||||||
|
<% next_time = work_times[i+1] %>
|
||||||
|
<% next_time = "Now" if next_time.nil? %>
|
||||||
|
<%= "#{work_time} ~ #{next_time}" %>
|
||||||
|
<br>
|
||||||
|
<% end %>
|
||||||
|
<div style="clear: both;"></div>
|
||||||
|
<span class="<%=sub_task.status%>_label"><i class="fa <%=klass%>"></i><%=t("ruling_timer.#{sub_task.status}")%></span>( <%=t("ruling_timer.total")%>: <%=sub_task.get_infos["work"] rescue "00:00:00" %> )
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<br>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<%=
|
||||||
|
content_tag :div, class: "bottomnav clearfix" do
|
||||||
|
referer = request.referer rescue nil
|
||||||
|
referer = task_management_admin_ruling_timers_path if referer.blank? || request.host != URI.parse(URI.encode(referer)).host
|
||||||
|
link_to(t('ruling_timer.back'), referer, :class=>"btn")
|
||||||
|
end %>
|
||||||
|
<style>
|
||||||
|
.bottomnav{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,302 @@
|
||||||
|
<% if current_user %>
|
||||||
|
<%= csrf_meta_tags %>
|
||||||
|
<% @ruling_timer_temp = RulingTimerTemp.where(:user=>current_user).first %>
|
||||||
|
<% @ruling_timer_temp = RulingTimerTemp.new if @ruling_timer_temp.nil? %>
|
||||||
|
<% @ruling_timer_temp.check_and_store %>
|
||||||
|
<% @new_sub_tasks = RulingTimerSubTask.where(:user=>current_user,:status.ne=>"finish",:id.nin=>@ruling_timer_temp.sub_task_ids).to_a %>
|
||||||
|
<div class="timeman-container timeman-container-tc" id="timeman-container">
|
||||||
|
<div class="timeman-wrap">
|
||||||
|
<span id="timeman-block" class="timeman-block">
|
||||||
|
<span class="bx-time" id="timeman-timer">
|
||||||
|
<span class="current_time"><%=DateTime.now.new_offset('+08:00').strftime('%l:%M %p')%></span>
|
||||||
|
</span>
|
||||||
|
<span class="timeman-right-side" id="timeman-right">
|
||||||
|
<span class="timeman-beginning-but" id="timeman-status-block">
|
||||||
|
<% if @ruling_timer_temp.status == "working" %>
|
||||||
|
<i class="fa fa-stop-circle"></i>
|
||||||
|
<span id="timeman-status" class="timeman-status"><%=t("ruling_timer.working")%></span>
|
||||||
|
<% else %>
|
||||||
|
<i class="fa fa-play-circle"></i>
|
||||||
|
<span id="timeman-status" class="timeman-status">
|
||||||
|
<% if @ruling_timer_temp.status == "rest"%>
|
||||||
|
繼續
|
||||||
|
<% else %>
|
||||||
|
<% if @ruling_timer_temp.all_work_times_seconds == 0 %>
|
||||||
|
打卡上班
|
||||||
|
<% else %>
|
||||||
|
記錄的下班時間
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<% if @new_sub_tasks.count != 0 %>
|
||||||
|
<button class="display_task_list btn btn-danger" title="<%=t("ruling_timer.new_tasks_hint")%>">New!</button>
|
||||||
|
<% end %>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.display_task_list{
|
||||||
|
background: red;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
.timeman-container{
|
||||||
|
float: left;
|
||||||
|
background: <%=current_site.orbit_bar_background_color%>;
|
||||||
|
color: <%=current_site.orbit_bar_text_color%>;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.timeman-wrap{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.current_time{
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.timeman-block {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
text-align: left;
|
||||||
|
white-space: normal;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.timeman-right-side{
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
.timeman-beginning-but{
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
window.can_notify = true;
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
console.log('This browser does not support notification');
|
||||||
|
window.can_notify = false;
|
||||||
|
}
|
||||||
|
window.notify_icon_url = $('link[rel="shortcut icon"]').attr('href');
|
||||||
|
window.all_notifies = [];
|
||||||
|
window.add_notify = function(permission,time,end_time,title,body){
|
||||||
|
if(permission == "granted"){
|
||||||
|
all_notifies.push({"time": time,"end_time": end_time,"title": title,"body": body});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.create_notify = function(title,body,idx){
|
||||||
|
if(window.can_notify){
|
||||||
|
body = body || "";
|
||||||
|
var notify = new Notification(title,{"body": body,"icon": window.notify_icon_url,"tag": ("timer_notify_"+idx)});
|
||||||
|
if(idx != undefined){
|
||||||
|
notify.onclick = function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
window.all_notifies[idx] = null;
|
||||||
|
notify.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.time_calc = {};
|
||||||
|
time_calc.sub_interval = function(time_str,interval,get_last_only){
|
||||||
|
var time_arr = time_str.split(":");
|
||||||
|
var interval_arr = interval.split(":");
|
||||||
|
time_arr = time_arr.map(function(v,i){return (parseInt(v)-parseInt(interval_arr[i]))})
|
||||||
|
var tmp = 0;
|
||||||
|
time_arr = time_arr.map(function(v,i){
|
||||||
|
v += tmp;
|
||||||
|
var max_val = 60;
|
||||||
|
if(i == time_arr.length - 1){
|
||||||
|
return v;
|
||||||
|
}else{
|
||||||
|
tmp = (v * max_val);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
time_arr.reverse();
|
||||||
|
if(get_last_only){
|
||||||
|
return time_arr[0];
|
||||||
|
}
|
||||||
|
else if(time_arr[0] < 0){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tmp = 0;
|
||||||
|
time_arr = time_arr.map(function(v,i){
|
||||||
|
v += tmp;
|
||||||
|
var max_val = 60;
|
||||||
|
if(i == time_arr.length - 1)
|
||||||
|
max_val = 24;
|
||||||
|
tmp = Math.floor(v / max_val);
|
||||||
|
return (v % max_val);
|
||||||
|
})
|
||||||
|
time_arr.reverse();
|
||||||
|
time_arr = time_arr.map(function(v){return(v < 10 ? '0'+v : v)});
|
||||||
|
return time_arr.join(":");
|
||||||
|
}
|
||||||
|
time_calc.add_interval = function(time_str,interval){
|
||||||
|
var time_arr = time_str.split(":");
|
||||||
|
var interval_arr = interval.split(":");
|
||||||
|
time_arr = time_arr.map(function(v,i){return (parseInt(v)+parseInt(interval_arr[i]))})
|
||||||
|
time_arr.reverse();
|
||||||
|
var tmp = 0;
|
||||||
|
time_arr = time_arr.map(function(v,i){
|
||||||
|
v += tmp;
|
||||||
|
var max_val = 60;
|
||||||
|
if(i == time_arr.length - 1)
|
||||||
|
max_val = 24;
|
||||||
|
tmp = Math.floor(v / max_val);
|
||||||
|
return (v % max_val);
|
||||||
|
})
|
||||||
|
time_arr.reverse();
|
||||||
|
time_arr = time_arr.map(function(v){return(v < 10 ? '0'+v : v)});
|
||||||
|
return time_arr.join(":");
|
||||||
|
}
|
||||||
|
time_calc.get_next_hour = function(time_str){
|
||||||
|
var time_arr = time_str.split(":");
|
||||||
|
var hour = time_arr[0];
|
||||||
|
time_arr[0] = this.add_interval(hour,"1");
|
||||||
|
return time_arr.join(":");
|
||||||
|
}
|
||||||
|
time_calc.get_next_hour_only = function(time_str){
|
||||||
|
time_str = this.get_next_hour(time_str);
|
||||||
|
return time_str.replace(/:([:\d]+)/g,function(v){return v.replace(/\d/g,'0')});
|
||||||
|
}
|
||||||
|
window.timer_offset = (new Date().getTimezoneOffset() / -60).toString();
|
||||||
|
if(window.timer_offset[0] != "-"){
|
||||||
|
window.timer_offset = "+" + window.timer_offset;
|
||||||
|
}
|
||||||
|
window.time_tracker = {};
|
||||||
|
time_tracker.work_seconds = <%= @ruling_timer_temp.calc("work_times") %>;
|
||||||
|
time_tracker.rest_seconds = <%= @ruling_timer_temp.calc("rest_times") %>;
|
||||||
|
time_tracker.status = "<%= @ruling_timer_temp.status %>";
|
||||||
|
time_tracker.to_utc_date = function(date){
|
||||||
|
var new_date = new Date();
|
||||||
|
new_date.setTime(date.getTime() + date.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return new_date;
|
||||||
|
}
|
||||||
|
time_tracker.getPaddedComp = function(comp) {
|
||||||
|
return ((parseInt(comp) < 10) ? ('0' + comp) : comp)
|
||||||
|
}
|
||||||
|
time_tracker.transform_second_to_time = function(seconds){
|
||||||
|
var hour = 3600;
|
||||||
|
var minute = 60;
|
||||||
|
var total_hour = this.getPaddedComp(Math.floor(seconds / hour));
|
||||||
|
var rest_seconds = seconds % hour;
|
||||||
|
var total_minute = this.getPaddedComp(Math.floor(rest_seconds / minute));
|
||||||
|
var total_second = this.getPaddedComp(rest_seconds % minute);
|
||||||
|
return total_hour + ":" + total_minute + ":" + total_second;
|
||||||
|
}
|
||||||
|
time_tracker.getDateString = function(date, format,is_chinese) {
|
||||||
|
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
var week_days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
|
||||||
|
if(is_chinese){
|
||||||
|
months = [];
|
||||||
|
for(var i=0;i<12;i++){
|
||||||
|
months.push((i+1)+"月");
|
||||||
|
}
|
||||||
|
week_days = ["週日","週一","週二","週三","週四","週五","週六"]
|
||||||
|
}
|
||||||
|
var getPaddedComp = function(comp) {
|
||||||
|
return ((parseInt(comp) < 10) ? ('0' + comp) : comp)
|
||||||
|
},
|
||||||
|
formattedDate = format,
|
||||||
|
o = {
|
||||||
|
"y+": date.getFullYear() + (is_chinese ? "年" : ""), // year
|
||||||
|
"MM+": getPaddedComp(date.getMonth() + 1), //raw month
|
||||||
|
"M+": months[date.getMonth()], //month
|
||||||
|
"d+": (is_chinese ? (date.getDate() + "日") : getPaddedComp(date.getDate())), //day
|
||||||
|
"w+": week_days[date.getDay()], //weekday
|
||||||
|
"h+": getPaddedComp((date.getHours() > 12) ? date.getHours() % 12 : date.getHours()), //hour
|
||||||
|
"H+": getPaddedComp(date.getHours()), //hour
|
||||||
|
"m+": getPaddedComp(date.getMinutes()), //minute
|
||||||
|
"s+": getPaddedComp(date.getSeconds()), //second
|
||||||
|
"S+": getPaddedComp(date.getMilliseconds()), //millisecond,
|
||||||
|
"b+": (date.getHours() >= 12) ? 'PM' : 'AM'
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var k in o) {
|
||||||
|
if (new RegExp("(" + k + ")").test(format)) {
|
||||||
|
formattedDate = formattedDate.replace(RegExp.$1, o[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return formattedDate;
|
||||||
|
};
|
||||||
|
<% if @ruling_timer_temp.events.count != 0 %>
|
||||||
|
if(window.can_notify){
|
||||||
|
function add_events_notifies(permission){
|
||||||
|
var notify_title = "";
|
||||||
|
<% @ruling_timer_temp.events.each do |event| %>
|
||||||
|
var now_str = time_tracker.getDateString(new Date,"H:m")
|
||||||
|
notify_title = "<%="#{event["start"]} ~ #{event["end"]} : #{event["event"]}"%>";
|
||||||
|
if(time_calc.sub_interval("<%=event["end"]%>",now_str,true) > 0){
|
||||||
|
add_notify(permission,"<%=event["start"]%>","<%=event["end"]%>",notify_title);
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
}
|
||||||
|
if (Notification.permission === 'default' || Notification.permission === 'undefined') {
|
||||||
|
Notification.requestPermission(function(permission){
|
||||||
|
add_events_notifies(permission);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
add_events_notifies(Notification.permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
$(document).ready(function(){
|
||||||
|
var is_chinese = ( I18n && I18n.locale.indexOf('zh') != -1 );
|
||||||
|
time_tracker.datetime_format = is_chinese ? 'y M d h:m b' : 'd M, y h:m b';
|
||||||
|
time_tracker.date_format = is_chinese ? 'y M d' : 'd M, y';
|
||||||
|
time_tracker.time_format = "h:m b";
|
||||||
|
time_tracker.date_time_str_format = 'y/MM/d H:m';
|
||||||
|
time_tracker.short_day = (is_chinese ? "d (w)" : "w d");
|
||||||
|
time_tracker.short_date = (is_chinese ? "M d (w)" : "w d, M");
|
||||||
|
var update_interval = 60 * 1000;
|
||||||
|
var time_tracker_timeout_id;
|
||||||
|
function update_time(){
|
||||||
|
time_tracker.now_str = time_tracker.getDateString(new Date,"H:m");
|
||||||
|
$(".current_time").html(time_tracker.getDateString(new Date,time_tracker.time_format));
|
||||||
|
if(window.all_notifies.length != 0){
|
||||||
|
window.all_notifies.forEach(function(notify,i){
|
||||||
|
if(notify != null){
|
||||||
|
if(window.time_calc.sub_interval(notify["end_time"],time_tracker.now_str,true) <= 0){
|
||||||
|
window.all_notifies[i] = null;
|
||||||
|
}else{
|
||||||
|
var remain_time = window.time_calc.sub_interval(notify["time"],time_tracker.now_str,true);
|
||||||
|
if(remain_time <= 120){
|
||||||
|
window.create_notify(notify["title"],notify["body"],i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var update_interval = 60 * 1000;
|
||||||
|
if(time_tracker_timeout_id)
|
||||||
|
window.clearTimeout(time_tracker_timeout_id);
|
||||||
|
time_tracker_timeout_id = window.setTimeout(update_time,update_interval)
|
||||||
|
}
|
||||||
|
update_time();
|
||||||
|
window.show_timer_window = function(need_display){
|
||||||
|
$("#timeman_main").css("display","");
|
||||||
|
if($("#timeman_main").hasClass("active_popup") && need_display !== true)
|
||||||
|
$("#timeman_main").removeClass("active_popup");
|
||||||
|
else{
|
||||||
|
$("#timeman_main").addClass("active_popup");
|
||||||
|
if($.fn.scrollingTabs)
|
||||||
|
$('#timeman_main .nav-pills').scrollingTabs('refresh');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#timeman-container #timeman-timer,#timeman-container .timeman-right-side").off("click").on("click",show_timer_window)
|
||||||
|
$(document).on("click",":not(.timeman-wrap,#timeman_main)",function(event){
|
||||||
|
var target = $(event.target);
|
||||||
|
if(target.is(":not(.timeman-wrap,#timeman_main,#ui-datepicker-div)")){
|
||||||
|
if(target.parents(".timeman-wrap,#timeman_main,#menu-popup-task,.popup-window,#ui-datepicker-div").length == 0){
|
||||||
|
$(".popup-window").css("display","none").removeClass("popup").removeClass("active_popup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,793 @@
|
||||||
|
<% if (current_user rescue false)%>
|
||||||
|
<% @ruling_timer_temp.reset_all %>
|
||||||
|
<% @timer_infos = @ruling_timer_temp.get_infos %>
|
||||||
|
<div class="popup-window" id="timeman_main" style="display: none; position: fixed; left: 0; z-index: 1400 !important;">
|
||||||
|
<div class="popup-window-angly popup-window-angly-top" style="left: 130px; margin-left: 0px;"></div>
|
||||||
|
<div id="popup-window-content-timeman_main" class="popup-window-content">
|
||||||
|
<div class="tm-popup-content">
|
||||||
|
<div class="tm-popup-notice">
|
||||||
|
<span class="tm-popup-notice-text">工作日期間: </span>
|
||||||
|
<span class="tm-popup-notice-time"><%=@timer_infos["work"]%></span>
|
||||||
|
<span class="tm-popup-notice-pencil"><i class="fa fa-pencil" aria-hidden="true"></i></span>
|
||||||
|
<span class="tm-popup-notice-right"></span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-timeman tm-popup-timeman-buttons-mode tm-popup-timeman-change-time-mode">
|
||||||
|
<div class="tm-popup-timeman-pause" style="<%= 'display: none;' if @ruling_timer_temp.status != "rest" %>">
|
||||||
|
<span class="tm-popup-timeman-pause-timer-caption">休息時數: <span class="tm-popup-timeman-pause-time"><%=@timer_infos["rest"]%></span></span>
|
||||||
|
</div>
|
||||||
|
<table cellspacing="0" class="tm-popup-timeman-layout">
|
||||||
|
<tbody>
|
||||||
|
<tr class="timer_button working_btn_groups <%= 'hide' if @ruling_timer_temp.status == "stop" %>">
|
||||||
|
<td class="tm-popup-timeman-layout-time">
|
||||||
|
<button class="btn btn-secondary <%= @ruling_timer_temp.status == "rest" ? "tm-btn-start" : "tm-btn-pause" %>">
|
||||||
|
<span class="text-start start_timer"><i class="fa fa-play" style="margin-right: 0.5em;"></i>繼續</span>
|
||||||
|
<span class="text-pause rest_timer"><i class="fa fa-pause" style="margin-right: 0.5em;"></i>休息時數</span>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td class="tm-popup-timeman-layout-button">
|
||||||
|
<div class="tm-popup-button-handler">
|
||||||
|
<button class="btn btn-danger stop_timer">
|
||||||
|
<span style="margin-right: 0.5em;">■</span>打卡下班
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="tm-popup-change-time-link <%= 'hide' if @ruling_timer_temp.status != 'stop' %>">變更下班打卡時間</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="timer_button stop_btn_groups <%= 'hide' if @ruling_timer_temp.status != "stop" %>">
|
||||||
|
<td class="tm-popup-timeman-layout-button">
|
||||||
|
<button class="btn btn-success start_timer">
|
||||||
|
<i class="fa fa-play" style="margin-right: 0.5em;"></i>
|
||||||
|
<% if @ruling_timer_temp.all_work_times_seconds == 0 %>
|
||||||
|
打卡上班
|
||||||
|
<% else %>
|
||||||
|
繼續工作日
|
||||||
|
<% end %>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><a class="btn btn-primary" href="<%= admin_ruling_timers_path rescue '#' %>"><%=t("ruling_timer.view_history")%></a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="tm-tabs-box">
|
||||||
|
<ul class="nav nav-pills tm-tabs">
|
||||||
|
<li class="tm-tab active">
|
||||||
|
<a href="#tm_plan" data-toggle="tab" aria-expanded="true">每日計劃</a>
|
||||||
|
</li>
|
||||||
|
<li class="tm-tab">
|
||||||
|
<a href="#tm_summary" data-toggle="tab" aria-expanded="true">每日摘要</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content tm-tabs-content">
|
||||||
|
<div class="tm-tab-content tab-pane fade active in" id="tm_plan">
|
||||||
|
<div class="bx-planner-content">
|
||||||
|
<div>
|
||||||
|
<div class="tm-popup-section tm-popup-section-tasks"><span class="tm-popup-section-text">今日任務</span><span class="tm-popup-section-right-link">從清單中選取</span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-tasks">
|
||||||
|
<div class="tm-task-list">
|
||||||
|
<% @ruling_timer_temp.tasks.to_a.each_with_index do |task,i| %>
|
||||||
|
<% checked = @ruling_timer_temp.tasks_finished.include?(i) %>
|
||||||
|
<div class="tm-task-item" data-task-id="<%=@ruling_timer_temp.sub_task_ids[i]%>">
|
||||||
|
<input class="tm-task-checkbox" type="checkbox" <%="checked=\"checked\"" if checked%>>
|
||||||
|
<span class="tm-task-name <%="task-finished" if checked %>"><%=task%></span>
|
||||||
|
<span class="tm-task-item-menu"><i class="fa fa-caret-down"></i></span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-task-form tm-popup-task-form-disabled">
|
||||||
|
<input type="text" class="tm-popup-task-form-textbox" placeholder="輸入新任務">
|
||||||
|
<button class="add_btn tm-popup-task-form-submit">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-events-empty">
|
||||||
|
<div class="tm-popup-section tm-popup-section-events">
|
||||||
|
<span class="tm-popup-section-text">事項</span>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-events">
|
||||||
|
<div class="tm-popup-event-list">
|
||||||
|
<% @ruling_timer_temp.events.to_a.each do |event| %>
|
||||||
|
<div class="tm-popup-event">
|
||||||
|
<span class="time_interval"><%=event["start"]%> - <%=event["end"]%></span><span class="tm-popup-event-name"><%=event["event"]%></span>
|
||||||
|
<button type="button" title="移除事項" class="add_btn delete_event">X</button>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-event-form tm-popup-event-form-disabled">
|
||||||
|
<input type="text" class="event-start-time" value="13:00">
|
||||||
|
<input type="text" class="event-end-time" value="14:00">
|
||||||
|
<input type="text" class="event-form-textbox" placeholder="新活動">
|
||||||
|
<button class="add_btn tm-popup-event-form-submit">+</button>
|
||||||
|
<!--
|
||||||
|
<div class="tm-popup-event-form-options">
|
||||||
|
<input type="checkbox" class="checkbox" id="bx_tm_absence_0.4349872404840247">
|
||||||
|
<label for="bx_tm_absence_0.4349872404840247">不在辦公室</label>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tm-tab-content tab-pane fade" id="tm_summary">
|
||||||
|
<div class="tm-popup-report">
|
||||||
|
<div class="tm-popup-report-text">
|
||||||
|
<textarea class="tm-popup-report-textarea" placeholder="撰寫您工作的簡報"><%=@ruling_timer_temp.summary%></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="tm-popup-report-buttons">
|
||||||
|
<button type="button" class="btn btn-success" disabled="true" id="save_summary">儲存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-window" id="menu-popup-task"
|
||||||
|
style="display: none;position: fixed;z-index: 1500 !important;">
|
||||||
|
<div class="popup-window-content" style="padding: 0px;">
|
||||||
|
<div class="menu-popup">
|
||||||
|
<div class="menu-popup-items">
|
||||||
|
<span class="menu-popup-item menu-popup-item-decline ">
|
||||||
|
<i class="fa fa-minus-circle"></i>
|
||||||
|
<span class="menu-popup-item-text">從每日計劃移除</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-window" id="task-list-popup"
|
||||||
|
style="display: none;position: fixed;z-index: 1500 !important;">
|
||||||
|
<div class="popup-window-content" style="padding: 0px;">
|
||||||
|
<div class="task-popup">
|
||||||
|
<div class="task-popup-items">
|
||||||
|
<% %>
|
||||||
|
<% if @new_sub_tasks.count == 0 %>
|
||||||
|
無任務可添加
|
||||||
|
<% else %>
|
||||||
|
<% @new_sub_tasks.each_with_index do |sub_task,i| %>
|
||||||
|
<div class="tm-task-new-item" data-task-id="<%=sub_task.ruling_timer_task_id.to_s%>">
|
||||||
|
<%=sub_task.task_name%>
|
||||||
|
<button type="button" title="添加任務" class="add_btn add_task_from_list">+</button>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-window-buttons">
|
||||||
|
<button class="popup-window-button close_popup">關閉</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-window" id="timer-setting-popup"
|
||||||
|
style="display: none;position: fixed;z-index: 1500 !important;">
|
||||||
|
<div class="popup-window-content" style="padding: 0px;">
|
||||||
|
<div class="task-popup" id="timer_content">
|
||||||
|
<% work_times = @ruling_timer_temp.get_work_times(true) %>
|
||||||
|
<% work_times.each_with_index do |t,i| %>
|
||||||
|
<% if i % 2 == 0
|
||||||
|
next_t = work_times[i+1]
|
||||||
|
else
|
||||||
|
next
|
||||||
|
end %>
|
||||||
|
<span><input name="work_times[]" class="timers timer_start" value="<%=t%>"> ~ <% if next_t %><input name="work_times[]" class="timers timer_end" value="<%=next_t%>"><%else%>Now<%end%></span>
|
||||||
|
<hr>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<div class="popup-window-buttons">
|
||||||
|
<button class="popup-window-button save_timers">儲存</button><button class="popup-window-button close_popup">關閉</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.timers {
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
.popup-window-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.popup-window-button{
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
text-decoration: underline;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0.2em;
|
||||||
|
}
|
||||||
|
.popup-window input{
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.tm-popup-change-time-link{
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
text-decoration: underline;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.nav:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.menu-popup-item-text{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tm-task-item-menu{
|
||||||
|
float: right;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.add_btn{
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #666666;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tm-popup-notice-pencil{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.popup-window{
|
||||||
|
background: #ffffff;
|
||||||
|
top: 48px;
|
||||||
|
padding: 10px;
|
||||||
|
-webkit-box-shadow: 0 7px 21px rgb(83 92 105 / 12%), 0 -1px 6px 0 rgb(83 92 105 / 6%);
|
||||||
|
box-shadow: 0 7px 21px rgb(83 92 105 / 12%), 0 -1px 6px 0 rgb(83 92 105 / 6%);
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
justify-content: stretch;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.popup-window.active_popup{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tm-popup-timeman-pause-timer-caption{
|
||||||
|
background: #868d95;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.timer_button{
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.tm-tabs-box ul{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.popup-window-angly {
|
||||||
|
height: 22px;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 33px;
|
||||||
|
}
|
||||||
|
.popup-window-angly-top {
|
||||||
|
display: block;
|
||||||
|
left: 10px;
|
||||||
|
margin: 0;
|
||||||
|
top: -22px;
|
||||||
|
}
|
||||||
|
.popup-window-angly:before {
|
||||||
|
background-color: #fff;
|
||||||
|
-webkit-box-shadow: 0 0 21px rgb(83 92 105 / 13%);
|
||||||
|
box-shadow: 0 0 21px rgb(83 92 105 / 13%);
|
||||||
|
content: '';
|
||||||
|
height: 15px;
|
||||||
|
position: absolute;
|
||||||
|
left: 9px;
|
||||||
|
top: 16px;
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
-webkit-transform-origin: 50% 50%;
|
||||||
|
-ms-transform-origin: 50% 50%;
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
.text-pause,.text-start{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tm-btn-pause .text-pause{
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
.tm-btn-start .text-start{
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
.tm-btn-start .text-pause{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.btn.btn-secondary{
|
||||||
|
background: #868d95;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn.btn-secondary:hover{
|
||||||
|
background: #5b6573;
|
||||||
|
}
|
||||||
|
.tm-popup-report-buttons {
|
||||||
|
padding: 10px 0 0 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tm-tab-content {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #c6cdd3;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.tm-popup-report-textarea{
|
||||||
|
width: 100%;
|
||||||
|
min-height: 130px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.tm-popup-section {
|
||||||
|
border-radius: 0;
|
||||||
|
height: 39px;
|
||||||
|
}
|
||||||
|
.tm-popup-section {
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
background: #e8e8e8;
|
||||||
|
font: normal normal normal 12px/23px Arial,Helvetica,sans-serif;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.tm-popup-section-tasks {
|
||||||
|
background: #b9e9fa;
|
||||||
|
}
|
||||||
|
.tm-popup-section-right-link {
|
||||||
|
color: #3a8090;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
opacity: .6;
|
||||||
|
margin-top: 12px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dashed #3a8090;
|
||||||
|
-webkit-transition: opacity .2s linear;
|
||||||
|
transition: opacity .2s linear;
|
||||||
|
}
|
||||||
|
.tm-popup-section-right-link {
|
||||||
|
position: absolute;
|
||||||
|
right: 11px;
|
||||||
|
top: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 21px;
|
||||||
|
color: #949494;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tm-popup-section-left, .tm-popup-section-text, .tm-popup-section-right {
|
||||||
|
display: inline-block;
|
||||||
|
height: 23px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.tm-popup-section-text {
|
||||||
|
color: #535c69;
|
||||||
|
height: 39px;
|
||||||
|
font: bold 14px/39px "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||||
|
padding-left: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.tm-popup-section-text {
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.tm-popup-section-events {
|
||||||
|
background: #ffe75e;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form-options {
|
||||||
|
width: auto;
|
||||||
|
padding: 2px 0 0 110px;
|
||||||
|
text-align: left;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form .tm-popup-event-start-time-textbox, .tm-popup-event-form .tm-popup-event-end-time-textbox {
|
||||||
|
padding: 0 4px !important;
|
||||||
|
text-align: center;
|
||||||
|
width: 55px;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form-options input {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 0;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.tm-popup-tasks {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form {
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
}
|
||||||
|
.tm-task-name{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.tm-task-name.task-finished{
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form input{
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
.tm-popup-event-form .event-start-time,.tm-popup-event-form .event-end-time{
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
.delete_event {
|
||||||
|
margin-left: 1em;
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
border-color: red;
|
||||||
|
}
|
||||||
|
.tm-popup-event-name{
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
.wrong_timer{
|
||||||
|
border: 2px solid red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function update_timer_inputs(){
|
||||||
|
$.post("/xhr/ruling_timer/get_work_times").done(function(work_times){
|
||||||
|
var input_html = "";
|
||||||
|
work_times.forEach(function(t,i){
|
||||||
|
var next_t;
|
||||||
|
if(i % 2 == 0){
|
||||||
|
next_t = work_times[i+1];
|
||||||
|
}else{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tmp = "";
|
||||||
|
tmp += ('<span><input name="work_times[]" class="timers timer_start" value="'+t+'"> ~ ');
|
||||||
|
if(next_t){
|
||||||
|
tmp += ('<input name="work_times[]" class="timers timer_end" value="'+next_t+'">');
|
||||||
|
}else{
|
||||||
|
tmp += "Now";
|
||||||
|
}
|
||||||
|
tmp += "</span><hr>";
|
||||||
|
input_html += tmp;
|
||||||
|
})
|
||||||
|
$("#timer_content").html(input_html);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$(document).ajaxSend(function(e, xhr, options) {
|
||||||
|
var token = $("meta[name='csrf-token']").attr('content');
|
||||||
|
xhr.setRequestHeader('X-CSRF-Token', token);
|
||||||
|
});
|
||||||
|
time_tracker.timer_id = null;
|
||||||
|
time_tracker.set_data = function(data){
|
||||||
|
this.work_seconds = data.work;
|
||||||
|
this.rest_seconds = data.rest;
|
||||||
|
}
|
||||||
|
time_tracker.start_timer = function(field){
|
||||||
|
var now = Date.now();
|
||||||
|
if(this[field+"start"] == undefined){
|
||||||
|
this[field+"start"] = now;
|
||||||
|
}
|
||||||
|
var temp = this[field] + Math.round((now - this[field+"start"]) / 1000);
|
||||||
|
this.field = field;
|
||||||
|
if(field == "work_seconds"){
|
||||||
|
$(".tm-popup-notice-time").text(this.transform_second_to_time(temp));
|
||||||
|
}else{
|
||||||
|
$(".tm-popup-timeman-pause-time").text(this.transform_second_to_time(temp));
|
||||||
|
}
|
||||||
|
if(time_tracker.timer_id){
|
||||||
|
window.clearTimeout(time_tracker.timer_id);
|
||||||
|
}
|
||||||
|
time_tracker.timer_id = window.setTimeout($.proxy(function (){
|
||||||
|
this.start_timer(field)
|
||||||
|
},this),1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
time_tracker.stop_timer = function(){
|
||||||
|
if(time_tracker.timer_id){
|
||||||
|
window.clearTimeout(time_tracker.timer_id);
|
||||||
|
}
|
||||||
|
var now = Date.now();
|
||||||
|
var field = this.field;
|
||||||
|
if(time_tracker[field+"start"] == undefined){
|
||||||
|
time_tracker[field+"start"] = now;
|
||||||
|
}
|
||||||
|
time_tracker[field] = time_tracker[field] + Math.round((now - time_tracker[field+"start"]) / 1000);
|
||||||
|
delete time_tracker[field+"start"];
|
||||||
|
}
|
||||||
|
if(time_tracker.status == "working"){
|
||||||
|
time_tracker.start_timer("work_seconds");
|
||||||
|
}else if(time_tracker.status == "rest"){
|
||||||
|
time_tracker.start_timer("rest_seconds");
|
||||||
|
}
|
||||||
|
$(".start_timer").click(function(){
|
||||||
|
var _this = $(this);
|
||||||
|
$.post("/xhr/ruling_timer/start",{"timer_offset": window.timer_offset}).done(
|
||||||
|
function(data){
|
||||||
|
$('.tm-popup-notice-time').text(time_tracker.transform_second_to_time(data.work));
|
||||||
|
$('.tm-popup-timeman-pause-time').text(time_tracker.transform_second_to_time(data.rest));
|
||||||
|
time_tracker.status = "working";
|
||||||
|
$(".stop_btn_groups").addClass("hide");
|
||||||
|
$(".working_btn_groups").removeClass("hide");
|
||||||
|
_this.parent().removeClass("tm-btn-start").addClass("tm-btn-pause");
|
||||||
|
time_tracker.set_data(data);
|
||||||
|
time_tracker.start_timer("work_seconds");
|
||||||
|
$(".tm-popup-timeman-pause").css("display","none");
|
||||||
|
$(".tm-popup-change-time-link").addClass("hide");
|
||||||
|
$('#timeman-status-block').html('<i class="fa fa-stop-circle"></i><span id="timeman-status" class="timeman-status"><%=t("ruling_timer.working")%></span>');
|
||||||
|
update_timer_inputs();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
$(".stop_timer").click(function(){
|
||||||
|
var _this = $(this);
|
||||||
|
$.post("/xhr/ruling_timer/stop",{"timer_offset": window.timer_offset}).done(
|
||||||
|
function(data){
|
||||||
|
$('.tm-popup-notice-time').text(time_tracker.transform_second_to_time(data.work));
|
||||||
|
$('.tm-popup-timeman-pause-time').text(time_tracker.transform_second_to_time(data.rest));
|
||||||
|
time_tracker.status = "stop";
|
||||||
|
$(".stop_btn_groups").removeClass("hide");
|
||||||
|
$(".working_btn_groups").addClass("hide");
|
||||||
|
time_tracker.stop_timer();
|
||||||
|
time_tracker.set_data(data);
|
||||||
|
$(".tm-popup-change-time-link").removeClass("hide");
|
||||||
|
$('#timeman-status-block').html('<i class="fa fa-play-circle"></i><span id="timeman-status" class="timeman-status">記錄的下班時間</span>');
|
||||||
|
update_timer_inputs();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
$(".rest_timer").click(function(){
|
||||||
|
var _this = $(this);
|
||||||
|
$.post("/xhr/ruling_timer/rest",{"timer_offset": window.timer_offset}).done(
|
||||||
|
function(data){
|
||||||
|
$('.tm-popup-notice-time').text(time_tracker.transform_second_to_time(data.work));
|
||||||
|
$('.tm-popup-timeman-pause-time').text(time_tracker.transform_second_to_time(data.rest));
|
||||||
|
time_tracker.status = "rest";
|
||||||
|
$(".stop_btn_groups").addClass("hide");
|
||||||
|
$(".working_btn_groups").removeClass("hide");
|
||||||
|
_this.parent().addClass("tm-btn-start").removeClass("tm-btn-pause");
|
||||||
|
time_tracker.set_data(data);
|
||||||
|
time_tracker.start_timer("rest_seconds");
|
||||||
|
$(".tm-popup-timeman-pause").css("display","");
|
||||||
|
$('#timeman-status-block').html('<i class="fa fa-play-circle"></i><span id="timeman-status" class="timeman-status"><%=t("ruling_timer.rest")%></span>');
|
||||||
|
update_timer_inputs();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
$(".tm-popup-report-textarea").on("input",function(){
|
||||||
|
$("#save_summary").removeAttr("disabled");
|
||||||
|
})
|
||||||
|
$("#save_summary").click(function(){
|
||||||
|
var summary = $(".tm-popup-report-textarea").val();
|
||||||
|
$.post("/xhr/ruling_timer/set_summary",{"summary": summary}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
$("#save_summary").attr("disabled","true");
|
||||||
|
}else{
|
||||||
|
alert("Cannot save summary!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
$(window).on("load",function(){
|
||||||
|
$('.popup-window-content input').off('focus');
|
||||||
|
})
|
||||||
|
$(".tm-popup-task-form-textbox").on("keyup",function(){
|
||||||
|
event.preventDefault();
|
||||||
|
var keyCode = event.which ? event.which : event.keyCode;
|
||||||
|
if( keyCode == 13 ){
|
||||||
|
$(".tm-popup-task-form-submit").click();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(".tm-popup-task-form-submit").click(function(){
|
||||||
|
var task = $(".tm-popup-task-form-textbox").val();
|
||||||
|
if(task != ""){
|
||||||
|
$.post("/xhr/ruling_timer/add_task",{"task": task}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
var task_id = data["task-id"];
|
||||||
|
var task_item = $("<div class=\"tm-task-item\" data-task-id=\""+task_id+"\"></div>");
|
||||||
|
task_item.append("<input class=\"tm-task-checkbox\" type=\"checkbox\">"+"<span class=\"tm-task-name\">"+task+"</span>");
|
||||||
|
task_item.append("<span class=\"tm-task-item-menu\"><i class=\"fa fa-caret-down\"></i></span>");
|
||||||
|
$(".tm-task-list").append(task_item);
|
||||||
|
$(".tm-popup-task-form-textbox").val("");
|
||||||
|
}else{
|
||||||
|
alert("Cannot save task!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(".tm-task-checkbox").click(function(){
|
||||||
|
var task_name = $(this).siblings(".tm-task-name");
|
||||||
|
var task_id = $(this).parent().data("task-id");
|
||||||
|
var status = $(this).is(":checked") ? 1 : 0;
|
||||||
|
if(task_id){
|
||||||
|
$.post("/xhr/ruling_timer/set_task_status",{"task_id": task_id, "status": status}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
if(status == 1)
|
||||||
|
task_name.addClass("task-finished");
|
||||||
|
else
|
||||||
|
task_name.removeClass("task-finished");
|
||||||
|
}else{
|
||||||
|
alert("Cannot save task!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).on("click",".tm-task-item-menu",function(){
|
||||||
|
var offset = $(this).offset();
|
||||||
|
var task_id = $(this).parent().data("task-id");
|
||||||
|
if($("#menu-popup-task").hasClass("popup")){
|
||||||
|
$("#menu-popup-task").css("display","none").removeClass("popup");
|
||||||
|
}else{
|
||||||
|
$("#menu-popup-task").css("display","block").addClass("popup");
|
||||||
|
var popup_width = $("#menu-popup-task").width();
|
||||||
|
offset.top = offset.top + $(this).height();
|
||||||
|
var window_width = $(window).width();
|
||||||
|
var padding_right = 20;
|
||||||
|
var left = Math.min(offset.left - popup_width / 2 - padding_right, window_width - popup_width - padding_right);
|
||||||
|
offset.left = left;
|
||||||
|
$("#menu-popup-task").offset(offset)
|
||||||
|
$(".menu-popup-item-text").data("task-id",task_id);
|
||||||
|
$(".menu-popup-item-text").data("task_item",$(this).parent());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).on("click",".menu-popup-item-text",function(){
|
||||||
|
var task_id = $(this).data("task-id");
|
||||||
|
var _this = $(this);
|
||||||
|
if( task_id ){
|
||||||
|
$.post("/xhr/ruling_timer/remove_task",{"task_id": task_id}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
_this.data("task_item").remove();
|
||||||
|
$("#menu-popup-task").removeClass("popup").css("display","none");
|
||||||
|
}else{
|
||||||
|
alert("Cannot remove task!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
window.show_timer_task_list = function(){
|
||||||
|
var _this = $(".tm-popup-section-right-link");
|
||||||
|
var popup = $("#task-list-popup");
|
||||||
|
popup.css("display","block");
|
||||||
|
var offset = _this.offset();
|
||||||
|
var item_width = _this.width();
|
||||||
|
offset.left += item_width / 2;
|
||||||
|
var popup_width = popup.width();
|
||||||
|
offset.top = offset.top + _this.height();
|
||||||
|
var window_width = $(window).width();
|
||||||
|
var padding_right = 20;
|
||||||
|
var left = Math.min(offset.left - popup_width / 2 - padding_right, window_width - popup_width - padding_right);
|
||||||
|
offset.left = left;
|
||||||
|
popup.offset(offset);
|
||||||
|
}
|
||||||
|
window.show_timer_setting = function(){
|
||||||
|
var _this = $(".tm-popup-notice-pencil");
|
||||||
|
var popup = $("#timer-setting-popup");
|
||||||
|
popup.css("display","block");
|
||||||
|
var offset = _this.offset();
|
||||||
|
var item_width = _this.width();
|
||||||
|
offset.left += item_width / 2;
|
||||||
|
var popup_width = popup.width();
|
||||||
|
offset.top = offset.top + _this.height();
|
||||||
|
var window_width = $(window).width();
|
||||||
|
var padding_right = 20;
|
||||||
|
var left = Math.min(offset.left - popup_width / 2 - padding_right, window_width - popup_width - padding_right);
|
||||||
|
offset.left = left;
|
||||||
|
popup.offset(offset);
|
||||||
|
}
|
||||||
|
$(".tm-popup-section-right-link").click(window.show_timer_task_list);
|
||||||
|
$(document).on("click",".close_popup",function(){
|
||||||
|
$(this).parents(".popup-window").eq(0).css("display","none");
|
||||||
|
})
|
||||||
|
$(".tm-popup-event-form-submit").click(function(){
|
||||||
|
var event = $(".tm-popup-event-form input").map(function(i,v){return v.value}).toArray();
|
||||||
|
if(event[2].trim() != ""){
|
||||||
|
$.post("/xhr/ruling_timer/add_event",{"event": event}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
var event_item = $('<div class="tm-popup-event"></div>')
|
||||||
|
event_item.append('<span class="time_interval">'+ event[0] +' - '+ event[1] +'</span><span class="tm-popup-event-name">'+ event[2] +'</span>');
|
||||||
|
event_item.append('<button type="button" title="移除事項" class="add_btn delete_event">X</button>');
|
||||||
|
$(".tm-popup-event-list").append(event_item);
|
||||||
|
var next_event_time = time_calc.get_next_hour_only(event[1]);
|
||||||
|
$(".event-start-time").val(next_event_time);
|
||||||
|
$(".event-end-time").val(time_calc.get_next_hour_only(next_event_time));
|
||||||
|
$(".event-form-textbox").val("");
|
||||||
|
if(window.can_notify){
|
||||||
|
var notify_title = event[0] + " ~ " + event[1] + " : " + event[2];
|
||||||
|
if (Notification.permission === 'default' || Notification.permission === 'undefined') {
|
||||||
|
Notification.requestPermission(function(permission){
|
||||||
|
add_notify(permission,event[0],event[1],notify_title)
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
add_notify(Notification.permission,event[0],event[1],notify_title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
alert("Cannot add event!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).on("click",".delete_event",function(){
|
||||||
|
var event_idx = $(this).parent().index();
|
||||||
|
var _this = $(this);
|
||||||
|
$.post("/xhr/ruling_timer/delete_event",{"event_idx": event_idx}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
_this.parent().remove();
|
||||||
|
}else{
|
||||||
|
alert("Cannot remove event!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
$(".add_task_from_list").click(function(){
|
||||||
|
var task_id = $(this).parent().data("task-id");
|
||||||
|
var _this = $(this);
|
||||||
|
$.post("/xhr/ruling_timer/add_task_from_list",{"task_id": task_id}).done(function(data){
|
||||||
|
if(data.success){
|
||||||
|
_this.parent().remove();
|
||||||
|
if($(".task-popup-items > *").length == 0){
|
||||||
|
$("#task-list-popup").css("display","none");
|
||||||
|
}
|
||||||
|
var task_id = data["task-id"];
|
||||||
|
var task_item = $("<div class=\"tm-task-item\" data-task-id=\""+task_id+"\"></div>");
|
||||||
|
task_item.append("<input class=\"tm-task-checkbox\" type=\"checkbox\">"+"<span class=\"tm-task-name\">"+data.task+"</span>");
|
||||||
|
task_item.append("<span class=\"tm-task-item-menu\"><i class=\"fa fa-caret-down\"></i></span>");
|
||||||
|
$(".tm-task-list").append(task_item);
|
||||||
|
}else{
|
||||||
|
alert("Cannot add task!\nPlease refresh web and login!");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
$('.event-start-time,.event-end-time').click(function(){
|
||||||
|
$(this).not('.hasDatepicker').ui_timepicker({timeFormat: 'H:mm'});
|
||||||
|
$(this).trigger('focus');
|
||||||
|
})
|
||||||
|
$(document).on('click','.timers',function(){
|
||||||
|
$(this).not('.hasDatepicker').ui_timepicker({timeFormat: 'HH:mm:ss'});
|
||||||
|
$(this).trigger('focus');
|
||||||
|
})
|
||||||
|
$(".tm-popup-notice-pencil").click(show_timer_setting);
|
||||||
|
$(".save_timers").click(function(){
|
||||||
|
var _this = $(this);
|
||||||
|
var work_times = $('.timers').map(function(i,v){return v.value}).toArray();
|
||||||
|
var wrong_index = -1;
|
||||||
|
work_times.forEach(function(v,i){
|
||||||
|
work_times.slice(i+1,work_times.length).forEach(function(vv,j){
|
||||||
|
if(v > vv){
|
||||||
|
wrong_index = i;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if(wrong_index != -1){
|
||||||
|
var min = work_times[wrong_index-1];
|
||||||
|
var max = work_times[wrong_index+1];
|
||||||
|
var title = "";
|
||||||
|
if(min){
|
||||||
|
title += decodeURIComponent("<%=t('ruling_timer.please_input_larger_than_value')%>").replace("{{value}}",min) + "\n";
|
||||||
|
}
|
||||||
|
if(max){
|
||||||
|
title += decodeURIComponent("<%=t('ruling_timer.please_input_smaller_than_value')%>").replace("{{value}}",max) + "\n";
|
||||||
|
}
|
||||||
|
$('.timers').eq(wrong_index).addClass("wrong_timer").data({"min": min,"max": max}).attr("title",title);
|
||||||
|
}else{
|
||||||
|
$.post("/xhr/ruling_timer/save_work_times",{"work_times": work_times}).done(function(data){
|
||||||
|
var field = time_tracker.field;
|
||||||
|
time_tracker.stop_timer();
|
||||||
|
time_tracker.set_data(data);
|
||||||
|
time_tracker.start_timer(field);
|
||||||
|
_this.parents(".popup-window").eq(0).css("display","none");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).on("input change",".wrong_timer",function(){
|
||||||
|
var min = $(this).data("min");
|
||||||
|
var max = $(this).data("max");
|
||||||
|
var val = $(this).val();
|
||||||
|
var correct = true;
|
||||||
|
if(min && val < min){
|
||||||
|
correct = false;
|
||||||
|
}
|
||||||
|
if(max && val > max){
|
||||||
|
correct = false;
|
||||||
|
}
|
||||||
|
if(correct){
|
||||||
|
$(this).removeClass("wrong_timer").attr("title","");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
$(document).ready(function(){
|
||||||
|
<% if @ruling_timer_temp.events.count == 0 %>
|
||||||
|
var next_event_time = time_calc.get_next_hour_only(time_tracker.now_str);
|
||||||
|
<% else %>
|
||||||
|
var next_event_time = time_calc.get_next_hour_only("<%=@ruling_timer_temp.events.last["end"]%>");
|
||||||
|
<% end %>
|
||||||
|
$(".event-start-time").val(next_event_time);
|
||||||
|
$(".event-end-time").val(time_calc.get_next_hour_only(next_event_time));
|
||||||
|
$(".display_task_list").click(function(){
|
||||||
|
show_timer_window(true);
|
||||||
|
window.setTimeout(show_timer_task_list,100);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<% end %>
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# This command will automatically be run when you run "rails" with Rails gems
|
||||||
|
# installed from the root of your application.
|
||||||
|
|
||||||
|
ENGINE_ROOT = File.expand_path('..', __dir__)
|
||||||
|
ENGINE_PATH = File.expand_path('../lib/sync_ntnu_personal_data/engine', __dir__)
|
||||||
|
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
||||||
|
|
||||||
|
# Set up gems listed in the Gemfile.
|
||||||
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
||||||
|
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
||||||
|
|
||||||
|
require 'rails/all'
|
||||||
|
require 'rails/engine/commands'
|
|
@ -0,0 +1,55 @@
|
||||||
|
en:
|
||||||
|
restful_actions:
|
||||||
|
task_management: Task Management
|
||||||
|
timer_management: Timer Management
|
||||||
|
work_history: Work history
|
||||||
|
edit_temp_timer: Edit history
|
||||||
|
edit_temp_timer: Edit history
|
||||||
|
add_task: Add task
|
||||||
|
edit_task: Edit task
|
||||||
|
add_history: "Add history"
|
||||||
|
module_name:
|
||||||
|
ruling_timer: Ruling timer
|
||||||
|
ruling_timer:
|
||||||
|
please_input_larger_than_value: "Please input larger than {{value}}!"
|
||||||
|
please_input_smaller_than_value: "Please input smaller than {{value}}!"
|
||||||
|
view_history: "View history"
|
||||||
|
add_history: "Add history"
|
||||||
|
task_execution_time: "Task execution time"
|
||||||
|
back: Back
|
||||||
|
cancel: Cancel
|
||||||
|
confirm: Confirm
|
||||||
|
confirm_delete: "Confirm delete"
|
||||||
|
please_input_confirm_delete: "Please input 'Confirm delete' to here."
|
||||||
|
delete_history_hint1: "Do you really want to delete this history({{history}})?"
|
||||||
|
delete_history_hint: "Do you really want to delete this history?"
|
||||||
|
delete_task_hint1: "Do you really want to delete this task({{task}})?"
|
||||||
|
delete_task_hint: "Do you really want to delete this task?"
|
||||||
|
new_tasks_hint: "New tasks can be added!"
|
||||||
|
add_member: Add Member
|
||||||
|
delete_task: "Delete task"
|
||||||
|
add_task: Add task
|
||||||
|
task: Task
|
||||||
|
task_performer: Task performer
|
||||||
|
task_name: Task name
|
||||||
|
finish: Finish
|
||||||
|
stop: Stop
|
||||||
|
working: Working
|
||||||
|
rest: Rest
|
||||||
|
offline: Offline
|
||||||
|
member: Member
|
||||||
|
total: Total
|
||||||
|
ruling_timer: Ruling timer
|
||||||
|
user_work_history: "%{user}'s Work History"
|
||||||
|
my_work_history: My Work History
|
||||||
|
summary: Summary
|
||||||
|
date: Date
|
||||||
|
work_time: Work time
|
||||||
|
week_days:
|
||||||
|
sunday: Sunday
|
||||||
|
monday: Monday
|
||||||
|
tuesday: Tuesday
|
||||||
|
wednesday: Wednesday
|
||||||
|
thursday: Thursday
|
||||||
|
friday: Friday
|
||||||
|
saturday: Saturday
|
|
@ -0,0 +1,55 @@
|
||||||
|
zh_tw:
|
||||||
|
restful_actions:
|
||||||
|
task_management: 任務管理
|
||||||
|
timer_management: 計時管理
|
||||||
|
work_history: 工作紀錄
|
||||||
|
edit_temp_timer: 編輯紀錄
|
||||||
|
edit_timer: 編輯紀錄
|
||||||
|
add_task: 新增任務
|
||||||
|
edit_task: 編輯任務
|
||||||
|
add_history: 新增紀錄
|
||||||
|
module_name:
|
||||||
|
ruling_timer: 計時器模組
|
||||||
|
ruling_timer:
|
||||||
|
please_input_larger_than_value: "請輸入大於{{value}}的值!"
|
||||||
|
please_input_smaller_than_value: "請輸入小於{{value}}的值!"
|
||||||
|
view_history: 查看歷史紀錄
|
||||||
|
add_history: 新增紀錄
|
||||||
|
task_execution_time: "任務執行時間"
|
||||||
|
back: 回上一頁
|
||||||
|
cancel: 取消
|
||||||
|
confirm: 確認
|
||||||
|
confirm_delete: "確認刪除"
|
||||||
|
please_input_confirm_delete: "請輸入'確認刪除'。"
|
||||||
|
delete_history_hint1: "您真的想刪除此任務({{history}})嗎?"
|
||||||
|
delete_history_hint: "您真的想刪除此紀錄嗎?"
|
||||||
|
delete_task_hint1: "您真的想刪除此任務({{task}})嗎?"
|
||||||
|
delete_task_hint: "您真的想刪除此任務嗎?"
|
||||||
|
new_tasks_hint: 有新的任務可添加
|
||||||
|
add_member: 添加成員
|
||||||
|
delete_task: 刪除任務
|
||||||
|
add_task: 新增任務
|
||||||
|
task: 任務
|
||||||
|
task_performer: 任務執行者
|
||||||
|
task_name: 任務名稱
|
||||||
|
finish: 已完成
|
||||||
|
stop: 暫停
|
||||||
|
working: 工作中
|
||||||
|
rest: 休息中
|
||||||
|
offline: 離線
|
||||||
|
member: 成員
|
||||||
|
total: 共計
|
||||||
|
ruling_timer: 計時器模組
|
||||||
|
user_work_history: "%{user}的工作紀錄"
|
||||||
|
my_work_history: 我的工作紀錄
|
||||||
|
summary: 摘要
|
||||||
|
date: 日期
|
||||||
|
work_time: 工作時間
|
||||||
|
week_days:
|
||||||
|
sunday: 週日
|
||||||
|
monday: 週一
|
||||||
|
tuesday: 週二
|
||||||
|
wednesday: 週三
|
||||||
|
thursday: 週四
|
||||||
|
friday: 週五
|
||||||
|
saturday: 週六
|
|
@ -0,0 +1,55 @@
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
locales = Site.first.in_use_locales rescue I18n.available_locales
|
||||||
|
s = Site.first
|
||||||
|
save_flag = false
|
||||||
|
if !s.orbit_bar_extra_render_files_inside.include?("ruling_timers/timer_button")
|
||||||
|
s.orbit_bar_extra_render_files_inside << "ruling_timers/timer_button"
|
||||||
|
save_flag = true
|
||||||
|
end
|
||||||
|
if !s.orbit_bar_extra_render_files_outside.include?("ruling_timers/timer_window")
|
||||||
|
s.orbit_bar_extra_render_files_outside << "ruling_timers/timer_window"
|
||||||
|
save_flag = true
|
||||||
|
end
|
||||||
|
s.save if save_flag
|
||||||
|
namespace :admin do
|
||||||
|
resources :ruling_timers do
|
||||||
|
member do
|
||||||
|
post "delete_history"
|
||||||
|
get "add_history"
|
||||||
|
get "work_history"
|
||||||
|
get "edit_timer"
|
||||||
|
get "edit_temp_timer"
|
||||||
|
patch "update_timer"
|
||||||
|
patch "update_task"
|
||||||
|
get "edit_task"
|
||||||
|
post "delete_task"
|
||||||
|
get "view_task"
|
||||||
|
end
|
||||||
|
collection do
|
||||||
|
post "create_history"
|
||||||
|
patch "create_history"
|
||||||
|
get "timer_management"
|
||||||
|
get "task_management"
|
||||||
|
get "add_task"
|
||||||
|
post "create_task"
|
||||||
|
patch "create_task"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
scope "(:locale)", locale: Regexp.new(locales.join("|")) do
|
||||||
|
post "/xhr/ruling_timer/start" => "ruling_timers#start"
|
||||||
|
post "/xhr/ruling_timer/stop" => "ruling_timers#stop"
|
||||||
|
post "/xhr/ruling_timer/rest" => "ruling_timers#rest"
|
||||||
|
post "/xhr/ruling_timer/set_summary" => "ruling_timers#set_summary"
|
||||||
|
post "/xhr/ruling_timer/add_task" => "ruling_timers#add_task"
|
||||||
|
post "/xhr/ruling_timer/add_task_from_list" => "ruling_timers#add_task_from_list"
|
||||||
|
post "/xhr/ruling_timer/remove_task" => "ruling_timers#remove_task"
|
||||||
|
post "/xhr/ruling_timer/set_task_status" => "ruling_timers#set_task_status"
|
||||||
|
post "/xhr/ruling_timer/start_task" => "ruling_timers#start_task"
|
||||||
|
post "/xhr/ruling_timer/stop_task" => "ruling_timers#stop_task"
|
||||||
|
post "/xhr/ruling_timer/add_event" => "ruling_timers#add_event"
|
||||||
|
post "/xhr/ruling_timer/delete_event" => "ruling_timers#delete_event"
|
||||||
|
post "/xhr/ruling_timer/get_work_times" => "ruling_timers#get_work_times"
|
||||||
|
post "/xhr/ruling_timer/save_work_times" => "ruling_timers#save_work_times"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require "ruling_timer/engine"
|
||||||
|
|
||||||
|
module RulingTimer
|
||||||
|
# Your code goes here...
|
||||||
|
end
|
|
@ -0,0 +1,30 @@
|
||||||
|
module RulingTimer
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
initializer "ruling_timer" do
|
||||||
|
OrbitApp.registration "RulingTimer", :type => "ModuleApp" do
|
||||||
|
module_label "ruling_timer.ruling_timer"
|
||||||
|
base_url File.expand_path File.dirname(__FILE__)
|
||||||
|
authorizable
|
||||||
|
|
||||||
|
side_bar do
|
||||||
|
head_label_i18n 'ruling_timer.ruling_timer', icon_class: "icons-clock"
|
||||||
|
available_for "users"
|
||||||
|
active_for_controllers (['admin/seminars'])
|
||||||
|
head_link_path "admin_ruling_timers_path"
|
||||||
|
|
||||||
|
context_link 'restful_actions.timer_management',
|
||||||
|
:link_path=>"timer_management_admin_ruling_timers_path" ,
|
||||||
|
:priority=>1,
|
||||||
|
:active_for_action=>{'admin/ruling_timers'=>'timer_management'},
|
||||||
|
:available_for => 'managers'
|
||||||
|
context_link 'restful_actions.task_management',
|
||||||
|
:link_path=>"task_management_admin_ruling_timers_path" ,
|
||||||
|
:priority=>2,
|
||||||
|
:active_for_action=>{'admin/ruling_timers'=>'task_management'},
|
||||||
|
:available_for => 'managers'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
module RulingTimer
|
||||||
|
VERSION = '0.0.1'
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
$:.push File.expand_path("lib", __dir__)
|
||||||
|
|
||||||
|
# Maintain your gem's version:
|
||||||
|
require "ruling_timer/version"
|
||||||
|
# Describe your gem and declare its dependencies:
|
||||||
|
Gem::Specification.new do |spec|
|
||||||
|
spec.name = "ruling_timer"
|
||||||
|
spec.version = RulingTimer::VERSION
|
||||||
|
spec.authors = ["Bohung Chiu"]
|
||||||
|
spec.email = ["bohung@rulingcom.com"]
|
||||||
|
spec.homepage = "https://w3.rulingcom.com/"
|
||||||
|
spec.summary = "Ruling Timer for users"
|
||||||
|
spec.description = "Ruling Timer for users"
|
||||||
|
spec.license = "MIT"
|
||||||
|
|
||||||
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
||||||
|
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
||||||
|
if spec.respond_to?(:metadata)
|
||||||
|
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
||||||
|
else
|
||||||
|
raise "RubyGems 2.0 or newer is required to protect against " \
|
||||||
|
"public gem pushes."
|
||||||
|
end
|
||||||
|
|
||||||
|
spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
2.5.4
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||||
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||||
|
|
||||||
|
require_relative 'config/application'
|
||||||
|
|
||||||
|
Rails.application.load_tasks
|
|
@ -0,0 +1,2 @@
|
||||||
|
//= link_tree ../images
|
||||||
|
//= link_directory ../stylesheets .css
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||||
|
* listed below.
|
||||||
|
*
|
||||||
|
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||||
|
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
||||||
|
*
|
||||||
|
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
||||||
|
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
||||||
|
* files in this directory. Styles in this file should be added after the last require_* statement.
|
||||||
|
* It is generally better to create a new file per style scope.
|
||||||
|
*
|
||||||
|
*= require_tree .
|
||||||
|
*= require_self
|
||||||
|
*/
|
|
@ -0,0 +1,4 @@
|
||||||
|
module ApplicationCable
|
||||||
|
class Channel < ActionCable::Channel::Base
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
module ApplicationCable
|
||||||
|
class Connection < ActionCable::Connection::Base
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
class ApplicationController < ActionController::Base
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
module ApplicationHelper
|
||||||
|
end
|
|
@ -0,0 +1,15 @@
|
||||||
|
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||||
|
// listed below.
|
||||||
|
//
|
||||||
|
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||||
|
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
||||||
|
//
|
||||||
|
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||||
|
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
||||||
|
//
|
||||||
|
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
||||||
|
// about supported directives.
|
||||||
|
//
|
||||||
|
//= require rails-ujs
|
||||||
|
//= require activestorage
|
||||||
|
//= require_tree .
|
|
@ -0,0 +1,7 @@
|
||||||
|
class ApplicationJob < ActiveJob::Base
|
||||||
|
# Automatically retry jobs that encountered a deadlock
|
||||||
|
# retry_on ActiveRecord::Deadlocked
|
||||||
|
|
||||||
|
# Most jobs are safe to ignore if the underlying records are no longer available
|
||||||
|
# discard_on ActiveJob::DeserializationError
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
class ApplicationMailer < ActionMailer::Base
|
||||||
|
default from: 'from@example.com'
|
||||||
|
layout 'mailer'
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
|
self.abstract_class = true
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Dummy</title>
|
||||||
|
<%= csrf_meta_tags %>
|
||||||
|
<%= csp_meta_tag %>
|
||||||
|
|
||||||
|
<%= stylesheet_link_tag 'application', media: 'all' %>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<%= yield %>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<style>
|
||||||
|
/* Email styles need to be inline */
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<%= yield %>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1 @@
|
||||||
|
<%= yield %>
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
APP_PATH = File.expand_path('../config/application', __dir__)
|
||||||
|
require_relative '../config/boot'
|
||||||
|
require 'rails/commands'
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
require_relative '../config/boot'
|
||||||
|
require 'rake'
|
||||||
|
Rake.application.run
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
# path to your application root.
|
||||||
|
APP_ROOT = File.expand_path('..', __dir__)
|
||||||
|
|
||||||
|
def system!(*args)
|
||||||
|
system(*args) || abort("\n== Command #{args} failed ==")
|
||||||
|
end
|
||||||
|
|
||||||
|
FileUtils.chdir APP_ROOT do
|
||||||
|
# This script is a way to setup or update your development environment automatically.
|
||||||
|
# This script is idempotent, so that you can run it at anytime and get an expectable outcome.
|
||||||
|
# Add necessary setup steps to this file.
|
||||||
|
|
||||||
|
puts '== Installing dependencies =='
|
||||||
|
system! 'gem install bundler --conservative'
|
||||||
|
system('bundle check') || system!('bundle install')
|
||||||
|
|
||||||
|
# puts "\n== Copying sample files =="
|
||||||
|
# unless File.exist?('config/database.yml')
|
||||||
|
# FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
|
||||||
|
# end
|
||||||
|
|
||||||
|
puts "\n== Preparing database =="
|
||||||
|
system! 'bin/rails db:prepare'
|
||||||
|
|
||||||
|
puts "\n== Removing old logs and tempfiles =="
|
||||||
|
system! 'bin/rails log:clear tmp:clear'
|
||||||
|
|
||||||
|
puts "\n== Restarting application server =="
|
||||||
|
system! 'bin/rails restart'
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
# This file is used by Rack-based servers to start the application.
|
||||||
|
|
||||||
|
require_relative 'config/environment'
|
||||||
|
|
||||||
|
run Rails.application
|
|
@ -0,0 +1,19 @@
|
||||||
|
require_relative 'boot'
|
||||||
|
|
||||||
|
require 'rails/all'
|
||||||
|
|
||||||
|
Bundler.require(*Rails.groups)
|
||||||
|
require "sync_ntnu_personal_data"
|
||||||
|
|
||||||
|
module Dummy
|
||||||
|
class Application < Rails::Application
|
||||||
|
# Initialize configuration defaults for originally generated Rails version.
|
||||||
|
config.load_defaults 6.0
|
||||||
|
|
||||||
|
# Settings in config/environments/* take precedence over those specified here.
|
||||||
|
# Application configuration can go into files in config/initializers
|
||||||
|
# -- all .rb files in that directory are automatically loaded after loading
|
||||||
|
# the framework and any gems in your application.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Set up gems listed in the Gemfile.
|
||||||
|
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
|
||||||
|
|
||||||
|
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
||||||
|
$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__)
|
|
@ -0,0 +1,10 @@
|
||||||
|
development:
|
||||||
|
adapter: async
|
||||||
|
|
||||||
|
test:
|
||||||
|
adapter: test
|
||||||
|
|
||||||
|
production:
|
||||||
|
adapter: redis
|
||||||
|
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||||
|
channel_prefix: dummy_production
|
|
@ -0,0 +1,25 @@
|
||||||
|
# SQLite. Versions 3.8.0 and up are supported.
|
||||||
|
# gem install sqlite3
|
||||||
|
#
|
||||||
|
# Ensure the SQLite 3 gem is defined in your Gemfile
|
||||||
|
# gem 'sqlite3'
|
||||||
|
#
|
||||||
|
default: &default
|
||||||
|
adapter: sqlite3
|
||||||
|
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||||
|
timeout: 5000
|
||||||
|
|
||||||
|
development:
|
||||||
|
<<: *default
|
||||||
|
database: db/development.sqlite3
|
||||||
|
|
||||||
|
# Warning: The database defined as "test" will be erased and
|
||||||
|
# re-generated from your development database when you run "rake".
|
||||||
|
# Do not set this db to the same as development or production.
|
||||||
|
test:
|
||||||
|
<<: *default
|
||||||
|
database: db/test.sqlite3
|
||||||
|
|
||||||
|
production:
|
||||||
|
<<: *default
|
||||||
|
database: db/production.sqlite3
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Load the Rails application.
|
||||||
|
require_relative 'application'
|
||||||
|
|
||||||
|
# Initialize the Rails application.
|
||||||
|
Rails.application.initialize!
|
|
@ -0,0 +1,62 @@
|
||||||
|
Rails.application.configure do
|
||||||
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
|
# In the development environment your application's code is reloaded on
|
||||||
|
# every request. This slows down response time but is perfect for development
|
||||||
|
# since you don't have to restart the web server when you make code changes.
|
||||||
|
config.cache_classes = false
|
||||||
|
|
||||||
|
# Do not eager load code on boot.
|
||||||
|
config.eager_load = false
|
||||||
|
|
||||||
|
# Show full error reports.
|
||||||
|
config.consider_all_requests_local = true
|
||||||
|
|
||||||
|
# Enable/disable caching. By default caching is disabled.
|
||||||
|
# Run rails dev:cache to toggle caching.
|
||||||
|
if Rails.root.join('tmp', 'caching-dev.txt').exist?
|
||||||
|
config.action_controller.perform_caching = true
|
||||||
|
config.action_controller.enable_fragment_cache_logging = true
|
||||||
|
|
||||||
|
config.cache_store = :memory_store
|
||||||
|
config.public_file_server.headers = {
|
||||||
|
'Cache-Control' => "public, max-age=#{2.days.to_i}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
config.action_controller.perform_caching = false
|
||||||
|
|
||||||
|
config.cache_store = :null_store
|
||||||
|
end
|
||||||
|
|
||||||
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
|
config.active_storage.service = :local
|
||||||
|
|
||||||
|
# Don't care if the mailer can't send.
|
||||||
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
# Print deprecation notices to the Rails logger.
|
||||||
|
config.active_support.deprecation = :log
|
||||||
|
|
||||||
|
# Raise an error on page load if there are pending migrations.
|
||||||
|
config.active_record.migration_error = :page_load
|
||||||
|
|
||||||
|
# Highlight code that triggered database queries in logs.
|
||||||
|
config.active_record.verbose_query_logs = true
|
||||||
|
|
||||||
|
# Debug mode disables concatenation and preprocessing of assets.
|
||||||
|
# This option may cause significant delays in view rendering with a large
|
||||||
|
# number of complex assets.
|
||||||
|
config.assets.debug = true
|
||||||
|
|
||||||
|
# Suppress logger output for asset requests.
|
||||||
|
config.assets.quiet = true
|
||||||
|
|
||||||
|
# Raises error for missing translations.
|
||||||
|
# config.action_view.raise_on_missing_translations = true
|
||||||
|
|
||||||
|
# Use an evented file watcher to asynchronously detect changes in source code,
|
||||||
|
# routes, locales, etc. This feature depends on the listen gem.
|
||||||
|
# config.file_watcher = ActiveSupport::EventedFileUpdateChecker
|
||||||
|
end
|
|
@ -0,0 +1,112 @@
|
||||||
|
Rails.application.configure do
|
||||||
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
|
# Code is not reloaded between requests.
|
||||||
|
config.cache_classes = true
|
||||||
|
|
||||||
|
# Eager load code on boot. This eager loads most of Rails and
|
||||||
|
# your application in memory, allowing both threaded web servers
|
||||||
|
# and those relying on copy on write to perform better.
|
||||||
|
# Rake tasks automatically ignore this option for performance.
|
||||||
|
config.eager_load = true
|
||||||
|
|
||||||
|
# Full error reports are disabled and caching is turned on.
|
||||||
|
config.consider_all_requests_local = false
|
||||||
|
config.action_controller.perform_caching = true
|
||||||
|
|
||||||
|
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
|
||||||
|
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
|
||||||
|
# config.require_master_key = true
|
||||||
|
|
||||||
|
# Disable serving static files from the `/public` folder by default since
|
||||||
|
# Apache or NGINX already handles this.
|
||||||
|
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
|
||||||
|
|
||||||
|
# Compress CSS using a preprocessor.
|
||||||
|
# config.assets.css_compressor = :sass
|
||||||
|
|
||||||
|
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||||
|
config.assets.compile = false
|
||||||
|
|
||||||
|
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
|
||||||
|
# config.action_controller.asset_host = 'http://assets.example.com'
|
||||||
|
|
||||||
|
# Specifies the header that your server uses for sending files.
|
||||||
|
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
|
||||||
|
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
|
||||||
|
|
||||||
|
# Store uploaded files on the local file system (see config/storage.yml for options).
|
||||||
|
config.active_storage.service = :local
|
||||||
|
|
||||||
|
# Mount Action Cable outside main process or domain.
|
||||||
|
# config.action_cable.mount_path = nil
|
||||||
|
# config.action_cable.url = 'wss://example.com/cable'
|
||||||
|
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
|
||||||
|
|
||||||
|
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
|
||||||
|
# config.force_ssl = true
|
||||||
|
|
||||||
|
# Use the lowest log level to ensure availability of diagnostic information
|
||||||
|
# when problems arise.
|
||||||
|
config.log_level = :debug
|
||||||
|
|
||||||
|
# Prepend all log lines with the following tags.
|
||||||
|
config.log_tags = [ :request_id ]
|
||||||
|
|
||||||
|
# Use a different cache store in production.
|
||||||
|
# config.cache_store = :mem_cache_store
|
||||||
|
|
||||||
|
# Use a real queuing backend for Active Job (and separate queues per environment).
|
||||||
|
# config.active_job.queue_adapter = :resque
|
||||||
|
# config.active_job.queue_name_prefix = "dummy_production"
|
||||||
|
|
||||||
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
# Ignore bad email addresses and do not raise email delivery errors.
|
||||||
|
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||||
|
# config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
|
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||||
|
# the I18n.default_locale when a translation cannot be found).
|
||||||
|
config.i18n.fallbacks = true
|
||||||
|
|
||||||
|
# Send deprecation notices to registered listeners.
|
||||||
|
config.active_support.deprecation = :notify
|
||||||
|
|
||||||
|
# Use default logging formatter so that PID and timestamp are not suppressed.
|
||||||
|
config.log_formatter = ::Logger::Formatter.new
|
||||||
|
|
||||||
|
# Use a different logger for distributed setups.
|
||||||
|
# require 'syslog/logger'
|
||||||
|
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
|
||||||
|
|
||||||
|
if ENV["RAILS_LOG_TO_STDOUT"].present?
|
||||||
|
logger = ActiveSupport::Logger.new(STDOUT)
|
||||||
|
logger.formatter = config.log_formatter
|
||||||
|
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Do not dump schema after migrations.
|
||||||
|
config.active_record.dump_schema_after_migration = false
|
||||||
|
|
||||||
|
# Inserts middleware to perform automatic connection switching.
|
||||||
|
# The `database_selector` hash is used to pass options to the DatabaseSelector
|
||||||
|
# middleware. The `delay` is used to determine how long to wait after a write
|
||||||
|
# to send a subsequent read to the primary.
|
||||||
|
#
|
||||||
|
# The `database_resolver` class is used by the middleware to determine which
|
||||||
|
# database is appropriate to use based on the time delay.
|
||||||
|
#
|
||||||
|
# The `database_resolver_context` class is used by the middleware to set
|
||||||
|
# timestamps for the last write to the primary. The resolver uses the context
|
||||||
|
# class timestamps to determine how long to wait before reading from the
|
||||||
|
# replica.
|
||||||
|
#
|
||||||
|
# By default Rails will store a last write timestamp in the session. The
|
||||||
|
# DatabaseSelector middleware is designed as such you can define your own
|
||||||
|
# strategy for connection switching and pass that into the middleware through
|
||||||
|
# these configuration options.
|
||||||
|
# config.active_record.database_selector = { delay: 2.seconds }
|
||||||
|
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
|
||||||
|
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
|
||||||
|
end
|
|
@ -0,0 +1,48 @@
|
||||||
|
# The test environment is used exclusively to run your application's
|
||||||
|
# test suite. You never need to work with it otherwise. Remember that
|
||||||
|
# your test database is "scratch space" for the test suite and is wiped
|
||||||
|
# and recreated between test runs. Don't rely on the data there!
|
||||||
|
|
||||||
|
Rails.application.configure do
|
||||||
|
# Settings specified here will take precedence over those in config/application.rb.
|
||||||
|
|
||||||
|
config.cache_classes = false
|
||||||
|
|
||||||
|
# Do not eager load code on boot. This avoids loading your whole application
|
||||||
|
# just for the purpose of running a single test. If you are using a tool that
|
||||||
|
# preloads Rails for running tests, you may have to set it to true.
|
||||||
|
config.eager_load = false
|
||||||
|
|
||||||
|
# Configure public file server for tests with Cache-Control for performance.
|
||||||
|
config.public_file_server.enabled = true
|
||||||
|
config.public_file_server.headers = {
|
||||||
|
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show full error reports and disable caching.
|
||||||
|
config.consider_all_requests_local = true
|
||||||
|
config.action_controller.perform_caching = false
|
||||||
|
config.cache_store = :null_store
|
||||||
|
|
||||||
|
# Raise exceptions instead of rendering exception templates.
|
||||||
|
config.action_dispatch.show_exceptions = false
|
||||||
|
|
||||||
|
# Disable request forgery protection in test environment.
|
||||||
|
config.action_controller.allow_forgery_protection = false
|
||||||
|
|
||||||
|
# Store uploaded files on the local file system in a temporary directory.
|
||||||
|
config.active_storage.service = :test
|
||||||
|
|
||||||
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
# Tell Action Mailer not to deliver emails to the real world.
|
||||||
|
# The :test delivery method accumulates sent emails in the
|
||||||
|
# ActionMailer::Base.deliveries array.
|
||||||
|
config.action_mailer.delivery_method = :test
|
||||||
|
|
||||||
|
# Print deprecation notices to the stderr.
|
||||||
|
config.active_support.deprecation = :stderr
|
||||||
|
|
||||||
|
# Raises error for missing translations.
|
||||||
|
# config.action_view.raise_on_missing_translations = true
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# ActiveSupport::Reloader.to_prepare do
|
||||||
|
# ApplicationController.renderer.defaults.merge!(
|
||||||
|
# http_host: 'example.org',
|
||||||
|
# https: false
|
||||||
|
# )
|
||||||
|
# end
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Version of your assets, change this if you want to expire all your assets.
|
||||||
|
Rails.application.config.assets.version = '1.0'
|
||||||
|
|
||||||
|
# Add additional assets to the asset load path.
|
||||||
|
# Rails.application.config.assets.paths << Emoji.images_path
|
||||||
|
|
||||||
|
# Precompile additional assets.
|
||||||
|
# application.js, application.css, and all non-JS/CSS in the app/assets
|
||||||
|
# folder are already added.
|
||||||
|
# Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
|
||||||
|
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
|
||||||
|
|
||||||
|
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
|
||||||
|
# Rails.backtrace_cleaner.remove_silencers!
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Define an application-wide content security policy
|
||||||
|
# For further information see the following documentation
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
|
||||||
|
|
||||||
|
# Rails.application.config.content_security_policy do |policy|
|
||||||
|
# policy.default_src :self, :https
|
||||||
|
# policy.font_src :self, :https, :data
|
||||||
|
# policy.img_src :self, :https, :data
|
||||||
|
# policy.object_src :none
|
||||||
|
# policy.script_src :self, :https
|
||||||
|
# policy.style_src :self, :https
|
||||||
|
|
||||||
|
# # Specify URI for violation reports
|
||||||
|
# # policy.report_uri "/csp-violation-report-endpoint"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# If you are using UJS then enable automatic nonce generation
|
||||||
|
# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
|
||||||
|
|
||||||
|
# Set the nonce only to specific directives
|
||||||
|
# Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
|
||||||
|
|
||||||
|
# Report CSP violations to a specified URI
|
||||||
|
# For further information see the following documentation:
|
||||||
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
|
||||||
|
# Rails.application.config.content_security_policy_report_only = true
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Specify a serializer for the signed and encrypted cookie jars.
|
||||||
|
# Valid options are :json, :marshal, and :hybrid.
|
||||||
|
Rails.application.config.action_dispatch.cookies_serializer = :json
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Configure sensitive parameters which will be filtered from the log file.
|
||||||
|
Rails.application.config.filter_parameters += [:password]
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Add new inflection rules using the following format. Inflections
|
||||||
|
# are locale specific, and you may define rules for as many different
|
||||||
|
# locales as you wish. All of these examples are active by default:
|
||||||
|
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
|
# inflect.plural /^(ox)$/i, '\1en'
|
||||||
|
# inflect.singular /^(ox)en/i, '\1'
|
||||||
|
# inflect.irregular 'person', 'people'
|
||||||
|
# inflect.uncountable %w( fish sheep )
|
||||||
|
# end
|
||||||
|
|
||||||
|
# These inflection rules are supported but not enabled by default:
|
||||||
|
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
|
# inflect.acronym 'RESTful'
|
||||||
|
# end
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# Add new mime types for use in respond_to blocks:
|
||||||
|
# Mime::Type.register "text/richtext", :rtf
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
# This file contains settings for ActionController::ParamsWrapper which
|
||||||
|
# is enabled by default.
|
||||||
|
|
||||||
|
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
||||||
|
ActiveSupport.on_load(:action_controller) do
|
||||||
|
wrap_parameters format: [:json]
|
||||||
|
end
|
||||||
|
|
||||||
|
# To enable root element in JSON for ActiveRecord objects.
|
||||||
|
# ActiveSupport.on_load(:active_record) do
|
||||||
|
# self.include_root_in_json = true
|
||||||
|
# end
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Files in the config/locales directory are used for internationalization
|
||||||
|
# and are automatically loaded by Rails. If you want to use locales other
|
||||||
|
# than English, add the necessary files in this directory.
|
||||||
|
#
|
||||||
|
# To use the locales, use `I18n.t`:
|
||||||
|
#
|
||||||
|
# I18n.t 'hello'
|
||||||
|
#
|
||||||
|
# In views, this is aliased to just `t`:
|
||||||
|
#
|
||||||
|
# <%= t('hello') %>
|
||||||
|
#
|
||||||
|
# To use a different locale, set it with `I18n.locale`:
|
||||||
|
#
|
||||||
|
# I18n.locale = :es
|
||||||
|
#
|
||||||
|
# This would use the information in config/locales/es.yml.
|
||||||
|
#
|
||||||
|
# The following keys must be escaped otherwise they will not be retrieved by
|
||||||
|
# the default I18n backend:
|
||||||
|
#
|
||||||
|
# true, false, on, off, yes, no
|
||||||
|
#
|
||||||
|
# Instead, surround them with single quotes.
|
||||||
|
#
|
||||||
|
# en:
|
||||||
|
# 'true': 'foo'
|
||||||
|
#
|
||||||
|
# To learn more, please read the Rails Internationalization guide
|
||||||
|
# available at https://guides.rubyonrails.org/i18n.html.
|
||||||
|
|
||||||
|
en:
|
||||||
|
hello: "Hello world"
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Puma can serve each request in a thread from an internal thread pool.
|
||||||
|
# The `threads` method setting takes two numbers: a minimum and maximum.
|
||||||
|
# Any libraries that use thread pools should be configured to match
|
||||||
|
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
||||||
|
# and maximum; this matches the default thread size of Active Record.
|
||||||
|
#
|
||||||
|
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
|
||||||
|
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
||||||
|
threads min_threads_count, max_threads_count
|
||||||
|
|
||||||
|
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
||||||
|
#
|
||||||
|
port ENV.fetch("PORT") { 3000 }
|
||||||
|
|
||||||
|
# Specifies the `environment` that Puma will run in.
|
||||||
|
#
|
||||||
|
environment ENV.fetch("RAILS_ENV") { "development" }
|
||||||
|
|
||||||
|
# Specifies the `pidfile` that Puma will use.
|
||||||
|
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
|
||||||
|
|
||||||
|
# Specifies the number of `workers` to boot in clustered mode.
|
||||||
|
# Workers are forked web server processes. If using threads and workers together
|
||||||
|
# the concurrency of the application would be max `threads` * `workers`.
|
||||||
|
# Workers do not work on JRuby or Windows (both of which do not support
|
||||||
|
# processes).
|
||||||
|
#
|
||||||
|
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
||||||
|
|
||||||
|
# Use the `preload_app!` method when specifying a `workers` number.
|
||||||
|
# This directive tells Puma to first boot the application and load code
|
||||||
|
# before forking the application. This takes advantage of Copy On Write
|
||||||
|
# process behavior so workers use less memory.
|
||||||
|
#
|
||||||
|
# preload_app!
|
||||||
|
|
||||||
|
# Allow puma to be restarted by `rails restart` command.
|
||||||
|
plugin :tmp_restart
|
|
@ -0,0 +1,3 @@
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
Spring.watch(
|
||||||
|
".ruby-version",
|
||||||
|
".rbenv-vars",
|
||||||
|
"tmp/restart.txt",
|
||||||
|
"tmp/caching-dev.txt"
|
||||||
|
)
|
|
@ -0,0 +1,34 @@
|
||||||
|
test:
|
||||||
|
service: Disk
|
||||||
|
root: <%= Rails.root.join("tmp/storage") %>
|
||||||
|
|
||||||
|
local:
|
||||||
|
service: Disk
|
||||||
|
root: <%= Rails.root.join("storage") %>
|
||||||
|
|
||||||
|
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
|
||||||
|
# amazon:
|
||||||
|
# service: S3
|
||||||
|
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
|
||||||
|
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
|
||||||
|
# region: us-east-1
|
||||||
|
# bucket: your_own_bucket
|
||||||
|
|
||||||
|
# Remember not to checkin your GCS keyfile to a repository
|
||||||
|
# google:
|
||||||
|
# service: GCS
|
||||||
|
# project: your_project
|
||||||
|
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
|
||||||
|
# bucket: your_own_bucket
|
||||||
|
|
||||||
|
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
|
||||||
|
# microsoft:
|
||||||
|
# service: AzureStorage
|
||||||
|
# storage_account_name: your_account_name
|
||||||
|
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
|
||||||
|
# container: your_container_name
|
||||||
|
|
||||||
|
# mirror:
|
||||||
|
# service: Mirror
|
||||||
|
# primary: local
|
||||||
|
# mirrors: [ amazon, google, microsoft ]
|
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>The page you were looking for doesn't exist (404)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<style>
|
||||||
|
.rails-default-error-page {
|
||||||
|
background-color: #EFEFEF;
|
||||||
|
color: #2E2F30;
|
||||||
|
text-align: center;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 33em;
|
||||||
|
margin: 4em auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > div {
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #BBB;
|
||||||
|
border-top: #B00100 solid 4px;
|
||||||
|
border-top-left-radius: 9px;
|
||||||
|
border-top-right-radius: 9px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 7px 12% 0;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page h1 {
|
||||||
|
font-size: 100%;
|
||||||
|
color: #730E15;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > p {
|
||||||
|
margin: 0 0 1em;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #999;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-top-color: #DADADA;
|
||||||
|
color: #666;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="rails-default-error-page">
|
||||||
|
<!-- This file lives in public/404.html -->
|
||||||
|
<div class="dialog">
|
||||||
|
<div>
|
||||||
|
<h1>The page you were looking for doesn't exist.</h1>
|
||||||
|
<p>You may have mistyped the address or the page may have moved.</p>
|
||||||
|
</div>
|
||||||
|
<p>If you are the application owner check the logs for more information.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>The change you wanted was rejected (422)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<style>
|
||||||
|
.rails-default-error-page {
|
||||||
|
background-color: #EFEFEF;
|
||||||
|
color: #2E2F30;
|
||||||
|
text-align: center;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 33em;
|
||||||
|
margin: 4em auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > div {
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #BBB;
|
||||||
|
border-top: #B00100 solid 4px;
|
||||||
|
border-top-left-radius: 9px;
|
||||||
|
border-top-right-radius: 9px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 7px 12% 0;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page h1 {
|
||||||
|
font-size: 100%;
|
||||||
|
color: #730E15;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > p {
|
||||||
|
margin: 0 0 1em;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #999;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-top-color: #DADADA;
|
||||||
|
color: #666;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="rails-default-error-page">
|
||||||
|
<!-- This file lives in public/422.html -->
|
||||||
|
<div class="dialog">
|
||||||
|
<div>
|
||||||
|
<h1>The change you wanted was rejected.</h1>
|
||||||
|
<p>Maybe you tried to change something you didn't have access to.</p>
|
||||||
|
</div>
|
||||||
|
<p>If you are the application owner check the logs for more information.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,66 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>We're sorry, but something went wrong (500)</title>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<style>
|
||||||
|
.rails-default-error-page {
|
||||||
|
background-color: #EFEFEF;
|
||||||
|
color: #2E2F30;
|
||||||
|
text-align: center;
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 33em;
|
||||||
|
margin: 4em auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > div {
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #BBB;
|
||||||
|
border-top: #B00100 solid 4px;
|
||||||
|
border-top-left-radius: 9px;
|
||||||
|
border-top-right-radius: 9px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 7px 12% 0;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page h1 {
|
||||||
|
font-size: 100%;
|
||||||
|
color: #730E15;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rails-default-error-page div.dialog > p {
|
||||||
|
margin: 0 0 1em;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: #F7F7F7;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
border-right-color: #999;
|
||||||
|
border-left-color: #999;
|
||||||
|
border-bottom-color: #999;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-top-color: #DADADA;
|
||||||
|
color: #666;
|
||||||
|
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="rails-default-error-page">
|
||||||
|
<!-- This file lives in public/500.html -->
|
||||||
|
<div class="dialog">
|
||||||
|
<div>
|
||||||
|
<h1>We're sorry, but something went wrong.</h1>
|
||||||
|
</div>
|
||||||
|
<p>If you are the application owner check the logs for more information.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,7 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class NavigationTest < ActionDispatch::IntegrationTest
|
||||||
|
# test "the truth" do
|
||||||
|
# assert true
|
||||||
|
# end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class RulingTimer::Test < ActiveSupport::TestCase
|
||||||
|
test "truth" do
|
||||||
|
assert_kind_of Module, RulingTimer
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Configure Rails Environment
|
||||||
|
ENV["RAILS_ENV"] = "test"
|
||||||
|
|
||||||
|
require_relative "../test/dummy/config/environment"
|
||||||
|
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)]
|
||||||
|
require "rails/test_help"
|
||||||
|
|
||||||
|
# Filter out the backtrace from minitest while preserving the one from other libraries.
|
||||||
|
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
|
||||||
|
|
||||||
|
|
||||||
|
# Load fixtures from the engine
|
||||||
|
if ActiveSupport::TestCase.respond_to?(:fixture_path=)
|
||||||
|
ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__)
|
||||||
|
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
|
||||||
|
ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files"
|
||||||
|
ActiveSupport::TestCase.fixtures :all
|
||||||
|
end
|
Loading…
Reference in New Issue