mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
Added resume option to tasks
This commit is contained in:
parent
839fc6bb75
commit
84a117c84b
@ -65,7 +65,7 @@ class TaskController extends BaseController
|
||||
}
|
||||
|
||||
return $table->addColumn('start_time', function($model) { return Utils::fromSqlDateTime($model->start_time); })
|
||||
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->duration == -1 ? time() - strtotime($model->start_time) : $model->duration); })
|
||||
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->is_running ? time() - strtotime($model->start_time) : $model->duration); })
|
||||
->addColumn('description', function($model) { return $model->description; })
|
||||
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
|
||||
->addColumn('dropdown', function ($model) {
|
||||
@ -81,7 +81,7 @@ class TaskController extends BaseController
|
||||
|
||||
if ($model->invoice_number) {
|
||||
$str .= '<li>' . link_to("/invoices/{$model->invoice_public_id}/edit", trans('texts.view_invoice')) . '</li>';
|
||||
} elseif ($model->duration == -1) {
|
||||
} elseif ($model->is_running) {
|
||||
$str .= '<li><a href="javascript:stopTask('.$model->public_id.')">'.trans('texts.stop_task').'</a></li>';
|
||||
} elseif (!$model->deleted_at || $model->deleted_at == '0000-00-00') {
|
||||
$str .= '<li><a href="javascript:invoiceTask('.$model->public_id.')">'.trans('texts.invoice_task').'</a></li>';
|
||||
@ -107,7 +107,7 @@ class TaskController extends BaseController
|
||||
if ($model->invoice_number) {
|
||||
$class = 'success';
|
||||
$label = trans('texts.invoiced');
|
||||
} elseif ($model->duration == -1) {
|
||||
} elseif ($model->is_running) {
|
||||
$class = 'primary';
|
||||
$label = trans('texts.running');
|
||||
} else {
|
||||
@ -163,8 +163,7 @@ class TaskController extends BaseController
|
||||
'clientPublicId' => $task->client ? $task->client->public_id : 0,
|
||||
'method' => 'PUT',
|
||||
'url' => 'tasks/'.$publicId,
|
||||
'title' => trans('texts.edit_task'),
|
||||
'duration' => time() - strtotime($task->start_time),
|
||||
'title' => trans('texts.edit_task')
|
||||
];
|
||||
|
||||
$data = array_merge($data, self::getViewModel());
|
||||
@ -196,11 +195,7 @@ class TaskController extends BaseController
|
||||
|
||||
Session::flash('message', trans($publicId ? 'texts.updated_task' : 'texts.created_task'));
|
||||
|
||||
if (Input::get('action') == 'stop') {
|
||||
return Redirect::to("tasks");
|
||||
} else {
|
||||
return Redirect::to("tasks/{$task->public_id}/edit");
|
||||
}
|
||||
return Redirect::to("tasks/{$task->public_id}/edit");
|
||||
}
|
||||
|
||||
public function bulk()
|
||||
@ -228,7 +223,7 @@ class TaskController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
if ($task->duration == -1) {
|
||||
if ($task->is_running) {
|
||||
Session::flash('error', trans('texts.task_error_running'));
|
||||
return Redirect::to('tasks');
|
||||
} else if ($task->invoice_id) {
|
||||
|
@ -23,7 +23,7 @@ class TaskRepository
|
||||
})
|
||||
->where('contacts.deleted_at', '=', null)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.start_time', 'tasks.description', 'tasks.duration', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id');
|
||||
->select('tasks.public_id', 'clients.name as client_name', 'clients.public_id as client_public_id', 'contacts.first_name', 'contacts.email', 'contacts.last_name', 'invoices.invoice_status_id', 'tasks.start_time', 'tasks.description', 'tasks.duration', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', 'tasks.is_running');
|
||||
|
||||
if ($clientPublicId) {
|
||||
$query->where('clients.public_id', '=', $clientPublicId);
|
||||
@ -58,19 +58,31 @@ class TaskRepository
|
||||
}
|
||||
if (isset($data['description'])) {
|
||||
$task->description = trim($data['description']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['action'] == 'start') {
|
||||
$task->start_time = Carbon::now()->toDateTimeString();
|
||||
$task->duration = -1;
|
||||
} else if ($data['action'] == 'stop' && $task->duration == -1) {
|
||||
$task->duration = strtotime('now') - strtotime($task->start_time);
|
||||
} else if ($data['action'] == 'save' && $task->duration != -1) {
|
||||
$task->is_running = true;
|
||||
} else if ($data['action'] == 'resume') {
|
||||
$task->break_duration = strtotime('now') - strtotime($task->start_time) + $task->duration;
|
||||
$task->resume_time = Carbon::now()->toDateTimeString();
|
||||
$task->is_running = true;
|
||||
} else if ($data['action'] == 'stop' && $task->is_running) {
|
||||
if ($task->resume_time) {
|
||||
$task->duration = $task->duration + strtotime('now') - strtotime($task->resume_time);
|
||||
$task->resume_time = null;
|
||||
} else {
|
||||
$task->duration = strtotime('now') - strtotime($task->start_time);
|
||||
}
|
||||
$task->is_running = false;
|
||||
} else if ($data['action'] == 'save' && !$task->is_running) {
|
||||
$task->start_time = $data['start_time'];
|
||||
$task->duration = $data['duration'];
|
||||
$task->break_duration = $data['break_duration'];
|
||||
}
|
||||
|
||||
$task->duration = max($task->duration, -1);
|
||||
$task->duration = max($task->duration, 0);
|
||||
$task->break_duration = max($task->break_duration, 0);
|
||||
|
||||
$task->save();
|
||||
|
||||
|
@ -21,7 +21,7 @@ class AddTasks extends Migration {
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->timestamp('start_time');
|
||||
$table->timestamp('start_time')->nullable();
|
||||
$table->integer('duration')->nullable();
|
||||
$table->string('description')->nullable();
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class EnableResumingTasks extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function($table)
|
||||
{
|
||||
$table->boolean('is_running')->default(false);
|
||||
$table->integer('break_duration')->nullable();
|
||||
$table->timestamp('resume_time')->nullable();
|
||||
});
|
||||
|
||||
$tasks = DB::table('tasks')
|
||||
->where('duration', '=', -1)
|
||||
->select('id', 'duration', 'start_time')
|
||||
->get();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$data = [
|
||||
'is_running' => true,
|
||||
'duration' => 0,
|
||||
|
||||
];
|
||||
|
||||
DB::table('tasks')
|
||||
->where('id', $task->id)
|
||||
->update($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tasks', function($table)
|
||||
{
|
||||
$table->dropColumn('is_running');
|
||||
$table->dropColumn('resume_time');
|
||||
$table->dropColumn('end_time');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -687,6 +687,8 @@ return array(
|
||||
'pro_plan_feature7' => 'Customize Invoice Field Titles & Numbering',
|
||||
'pro_plan_feature8' => 'Option to Attach PDFs to Client Emails',
|
||||
|
||||
|
||||
'resume' => 'Resume',
|
||||
'break_duration' => 'Break',
|
||||
'edit_details' => 'Edit Details',
|
||||
|
||||
);
|
||||
|
@ -28,6 +28,7 @@
|
||||
{!! Former::text('action') !!}
|
||||
{!! Former::text('start_time') !!}
|
||||
{!! Former::text('duration') !!}
|
||||
{!! Former::text('break_duration') !!}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@ -39,21 +40,37 @@
|
||||
{!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!}
|
||||
{!! Former::textarea('description')->rows(3) !!}
|
||||
|
||||
@if ($task && $task->duration == -1)
|
||||
@if ($task && $task->is_running)
|
||||
<center>
|
||||
<div id="duration-text" style="font-size: 36px; font-weight: 300; padding: 30px 0 20px 0"/>
|
||||
</center>
|
||||
@else
|
||||
@if (!$task)
|
||||
@if ($task)
|
||||
|
||||
<div class="form-group simple-time">
|
||||
<label for="simple-time" class="control-label col-lg-4 col-sm-4">
|
||||
</label>
|
||||
<div class="col-lg-8 col-sm-8" style="padding-top: 10px" id="editDetailsLink" >
|
||||
<p>{{ Utils::fromSqlDateTime($task->start_time) }}<p/>
|
||||
@if ($task->duration)
|
||||
{{ trans('texts.duration') }}: <span id="durationText"></span><br/>
|
||||
@endif
|
||||
@if ($task->break_duration)
|
||||
{{ trans('texts.break_duration') }}: <span id="breakDurationText"></span><br/>
|
||||
@endif
|
||||
<p>{!! Button::primary(trans('texts.edit_details'))->withAttributes(['onclick'=>'showTimeDetails()'])->small() !!}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@else
|
||||
{!! Former::radios('task_type')->radios([
|
||||
trans('texts.timer') => array('name' => 'task_type', 'value' => 'timer'),
|
||||
trans('texts.manual') => array('name' => 'task_type', 'value' => 'manual'),
|
||||
])->inline()->check('timer')->label(' ') !!}
|
||||
<div id="datetime-details" style="display: none">
|
||||
<br>
|
||||
@else
|
||||
<div>
|
||||
@endif
|
||||
<div id="datetime-details" style="display: none">
|
||||
|
||||
{!! Former::text('date')->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))
|
||||
->append('<i class="glyphicon glyphicon-calendar"></i>')->addGroupClass('date-group time-input') !!}
|
||||
|
||||
@ -89,6 +106,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-4 col-sm-4">
|
||||
{{ trans('texts.break_duration') }}
|
||||
</label>
|
||||
<div class="col-lg-8 col-sm-8 time-input">
|
||||
<input class="form-control" id="break_duration_hours" placeholder="{{ uctrans('texts.hours') }}"
|
||||
name="value" size="3" type="number" min="0" step="1"/>
|
||||
<input class="form-control" id="break_duration_minutes" placeholder="{{ uctrans('texts.minutes') }}"
|
||||
name="value" size="2" type="number" min="0" max="59" step="1"/>
|
||||
<input class="form-control" id="break_duration_seconds" placeholder="{{ uctrans('texts.seconds') }}"
|
||||
name="value" size="2" type="number" min="0" max="59" step="1"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group end-time">
|
||||
<label for="end-time" class="control-label col-lg-4 col-sm-4">
|
||||
{{ trans('texts.end') }}
|
||||
@ -108,16 +139,17 @@
|
||||
|
||||
|
||||
<center class="buttons">
|
||||
@if ($task && $task->duration == -1)
|
||||
@if ($task && $task->is_running)
|
||||
{!! Button::success(trans('texts.save'))->large()->appendIcon(Icon::create('floppy-disk'))->withAttributes(['id' => 'save-button']) !!}
|
||||
{!! Button::primary(trans('texts.stop'))->large()->appendIcon(Icon::create('stop'))->withAttributes(['id' => 'stop-button']) !!}
|
||||
@else
|
||||
{!! Button::normal(trans('texts.cancel'))->large()->asLinkTo(URL::to('/tasks'))->appendIcon(Icon::create('remove-circle')) !!}
|
||||
@if ($task)
|
||||
{!! Button::success(trans('texts.save'))->large()->appendIcon(Icon::create('floppy-disk'))->withAttributes(['id' => 'save-button']) !!}
|
||||
{!! Button::primary(trans('texts.resume'))->large()->appendIcon(Icon::create('play'))->withAttributes(['id' => 'resume-button']) !!}
|
||||
@else
|
||||
{!! Button::success(trans('texts.start'))->large()->appendIcon(Icon::create('play'))->withAttributes(['id' => 'start-button']) !!}
|
||||
{!! Button::success(trans('texts.save'))->large()->appendIcon(Icon::create('floppy-disk'))->withAttributes(['id' => 'save-button', 'style' => 'display:none']) !!}
|
||||
{!! Button::success(trans('texts.start'))->large()->appendIcon(Icon::create('play'))->withAttributes(['id' => 'start-button']) !!}
|
||||
@endif
|
||||
@endif
|
||||
</center>
|
||||
@ -128,15 +160,22 @@
|
||||
|
||||
|
||||
var clients = {!! $clients !!};
|
||||
var timeLabels = {};
|
||||
@foreach (['hour', 'minute', 'second'] as $period)
|
||||
timeLabels['{{ $period }}'] = '{{ trans("texts.{$period}") }}';
|
||||
timeLabels['{{ $period }}s'] = '{{ trans("texts.{$period}s") }}';
|
||||
@endforeach
|
||||
|
||||
function tock(duration) {
|
||||
var timeLabels = {};
|
||||
@foreach (['hour', 'minute', 'second'] as $period)
|
||||
timeLabels['{{ $period }}'] = '{{ trans("texts.{$period}") }}';
|
||||
timeLabels['{{ $period }}s'] = '{{ trans("texts.{$period}s") }}';
|
||||
@endforeach
|
||||
var str = convertDurationToString(duration);
|
||||
$('#duration-text').html(str);
|
||||
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
setTimeout(function() {
|
||||
tock(duration+1);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function convertDurationToString(duration) {
|
||||
var data = [];
|
||||
var periods = ['hour', 'minute', 'second'];
|
||||
var parts = secondsToTime(duration);
|
||||
@ -145,32 +184,33 @@
|
||||
var period = periods[i];
|
||||
var letter = period.charAt(0);
|
||||
var value = parts[letter];
|
||||
if (!value && !data.length) {
|
||||
if (!value || data.length) {
|
||||
continue;
|
||||
}
|
||||
period = value == 1 ? timeLabels[period] : timeLabels[period + 's'];
|
||||
data.push(value + ' ' + period);
|
||||
}
|
||||
|
||||
$('#duration-text').html(data.length ? data.join(', ') : '0 ' + timeLabels['seconds']);
|
||||
|
||||
setTimeout(function() {
|
||||
tock(duration+1);
|
||||
}, 1000);
|
||||
return data.length ? data.join(', ') : '0 ' + timeLabels['seconds'];
|
||||
}
|
||||
|
||||
function determineEndTime() {
|
||||
var startDate = moment($('#date').datepicker('getDate'));
|
||||
var parts = [$('#start_hours').val(), $('#start_minutes').val(), $('#start_seconds').val(), $('#start_ampm').val()];
|
||||
var date = moment(startDate.format('YYYY-MM-DD') + ' ' + parts.join(':'), 'YYYY-MM-DD h:m:s:a', true);
|
||||
|
||||
var duration = (parseInt($('#duration_seconds').val(), 10) || 0)
|
||||
+ (60 * (parseInt($('#duration_minutes').val(), 10) || 0))
|
||||
+ (60 * 60 * (parseInt($('#duration_hours').val(), 10)) || 0);
|
||||
|
||||
$('#start_time').val(date.utc().format("YYYY-MM-DD HH:mm:ss"));
|
||||
+ (60 * 60 * (parseInt($('#duration_hours').val(), 10)) || 0);
|
||||
$('#duration').val(duration);
|
||||
|
||||
date.add(duration, 's')
|
||||
var breakDuration = (parseInt($('#break_duration_seconds').val(), 10) || 0)
|
||||
+ (60 * (parseInt($('#break_duration_minutes').val(), 10) || 0))
|
||||
+ (60 * 60 * (parseInt($('#break_duration_hours').val(), 10)) || 0);
|
||||
$('#break_duration').val(breakDuration);
|
||||
|
||||
$('#start_time').val(date.utc().format("YYYY-MM-DD HH:mm:ss"));
|
||||
date.add(duration + breakDuration, 's')
|
||||
$('div.end-time div').html(date.local().calendar());
|
||||
}
|
||||
|
||||
@ -179,6 +219,11 @@
|
||||
$('.task-form').submit();
|
||||
}
|
||||
|
||||
function showTimeDetails() {
|
||||
$('#datetime-details').fadeIn();
|
||||
$('#editDetailsLink').hide();
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var $clientSelect = $('select#client');
|
||||
for (var i=0; i<clients.length; i++) {
|
||||
@ -229,6 +274,9 @@
|
||||
$('#stop-button').click(function() {
|
||||
submitAction('stop');
|
||||
});
|
||||
$('#resume-button').click(function() {
|
||||
submitAction('resume');
|
||||
});
|
||||
|
||||
$('.time-input').on('keyup change', (function() {
|
||||
determineEndTime();
|
||||
@ -236,8 +284,8 @@
|
||||
|
||||
@if ($task)
|
||||
NINJA.startTime = {{ strtotime($task->start_time) }};
|
||||
@if ($task->duration == -1)
|
||||
tock({{ $duration }});
|
||||
@if ($task->is_running)
|
||||
tock({{ $task->duration ?: strtotime('now') - strtotime($task->start_time) }});
|
||||
@else
|
||||
var date = new Date(NINJA.startTime * 1000);
|
||||
var hours = date.getHours();
|
||||
@ -261,6 +309,14 @@
|
||||
$('#duration_hours').val(parts['h']);
|
||||
$('#duration_minutes').val(parts['m']);
|
||||
$('#duration_seconds').val(parts['s']);
|
||||
|
||||
var parts = secondsToTime({{ $task->break_duration }});
|
||||
$('#break_duration_hours').val(parts['h']);
|
||||
$('#break_duration_minutes').val(parts['m']);
|
||||
$('#break_duration_seconds').val(parts['s']);
|
||||
|
||||
$('#durationText').html(convertDurationToString({{ $task->duration }}));
|
||||
$('#breakDurationText').html(convertDurationToString({{ $task->break_duration }}));
|
||||
@endif
|
||||
@endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user