1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Re-worked editing task details

This commit is contained in:
Hillel Coren 2015-07-12 22:43:45 +03:00
parent 9477c7467d
commit b9c00a0531
35 changed files with 804 additions and 289 deletions

View File

@ -647,6 +647,9 @@ class AccountController extends BaseController
$user->username = trim(Input::get('email'));
$user->email = trim(strtolower(Input::get('email')));
$user->phone = trim(Input::get('phone'));
if (Utils::isNinja()) {
$user->dark_mode = Input::get('dark_mode') ? true : false;
}
$user->save();
}

View File

@ -257,6 +257,8 @@ class AccountGatewayController extends BaseController
}
$accountGateway->accepted_credit_cards = $cardCount;
$accountGateway->show_address = Input::get('show_address') ? true : false;
$accountGateway->update_address = Input::get('update_address') ? true : false;
$accountGateway->config = json_encode($config);
if ($accountGatewayPublicId) {
@ -278,7 +280,7 @@ class AccountGatewayController extends BaseController
Session::flash('message', $message);
return Redirect::to('company/payments');
return Redirect::to("gateways/{$accountGateway->public_id}/edit");
}
}

View File

@ -375,10 +375,9 @@ class InvoiceController extends BaseController
'method' => 'POST',
'url' => 'invoices',
'title' => trans('texts.new_invoice'),
'client' => $client,
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null);
'client' => $client);
$data = array_merge($data, self::getViewModel());
return View::make('invoices.edit', $data);
}
@ -417,7 +416,7 @@ class InvoiceController extends BaseController
),
'recurringHelp' => $recurringHelp,
'invoiceLabels' => Auth::user()->account->getInvoiceLabels(),
'tasks' => Session::get('tasks') ? json_encode(Session::get('tasks')) : null,
];
}

View File

@ -233,33 +233,41 @@ class PaymentController extends BaseController
private function convertInputForOmnipay($input)
{
$country = Country::find($input['country_id']);
return [
$data = [
'firstName' => $input['first_name'],
'lastName' => $input['last_name'],
'number' => $input['card_number'],
'expiryMonth' => $input['expiration_month'],
'expiryYear' => $input['expiration_year'],
'cvv' => $input['cvv'],
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
];
if (isset($input['country_id'])) {
$country = Country::find($input['country_id']);
$data = array_merge($data, [
'billingAddress1' => $input['address1'],
'billingAddress2' => $input['address2'],
'billingCity' => $input['city'],
'billingState' => $input['state'],
'billingPostcode' => $input['postal_code'],
'billingCountry' => $country->iso_3166_2,
'shippingAddress1' => $input['address1'],
'shippingAddress2' => $input['address2'],
'shippingCity' => $input['city'],
'shippingState' => $input['state'],
'shippingPostcode' => $input['postal_code'],
'shippingCountry' => $country->iso_3166_2
]);
}
return $data;
}
private function getPaymentDetails($invitation, $input = null)
{
$invoice = $invitation->invoice;
$account = $invoice->account;
$key = $invoice->account_id.'-'.$invoice->invoice_number;
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
@ -330,6 +338,7 @@ class PaymentController extends BaseController
'currencyId' => $client->getCurrencyId(),
'account' => $client->account,
'hideLogo' => $account->isWhiteLabel(),
'showAddress' => $accountGateway->show_address,
];
return View::make('payments.payment', $data);
@ -498,19 +507,30 @@ class PaymentController extends BaseController
public function do_payment($invitationKey, $onSite = true, $useToken = false)
{
$rules = array(
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
$rules = [
'first_name' => 'required',
'last_name' => 'required',
'card_number' => 'required',
'expiration_month' => 'required',
'expiration_year' => 'required',
'cvv' => 'required',
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
);
];
if ($accountGateway->show_address) {
$rules = array_merge($rules, [
'address1' => 'required',
'city' => 'required',
'state' => 'required',
'postal_code' => 'required',
'country_id' => 'required',
]);
}
if ($onSite) {
$validator = Validator::make(Input::all(), $rules);
@ -522,23 +542,16 @@ class PaymentController extends BaseController
->withInput();
}
}
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
$invoice = $invitation->invoice;
$client = $invoice->client;
$account = $client->account;
$accountGateway = $account->getGatewayByType(Session::get('payment_type'));
/*
if ($onSite) {
if ($onSite && $accountGateway->update_address) {
$client->address1 = trim(Input::get('address1'));
$client->address2 = trim(Input::get('address2'));
$client->city = trim(Input::get('city'));
$client->state = trim(Input::get('state'));
$client->postal_code = trim(Input::get('postal_code'));
$client->country_id = Input::get('country_id');
$client->save();
}
*/
try {
$gateway = self::createGateway($accountGateway);

View File

@ -13,31 +13,19 @@ use DropdownButton;
use App\Models\Client;
use App\Models\Task;
/*
use Auth;
use Cache;
use App\Models\Activity;
use App\Models\Contact;
use App\Models\Invoice;
use App\Models\Size;
use App\Models\PaymentTerm;
use App\Models\Industry;
use App\Models\Currency;
use App\Models\Country;
*/
use App\Ninja\Repositories\TaskRepository;
use App\Ninja\Repositories\InvoiceRepository;
class TaskController extends BaseController
{
protected $taskRepo;
public function __construct(TaskRepository $taskRepo)
public function __construct(TaskRepository $taskRepo, InvoiceRepository $invoiceRepo)
{
parent::__construct();
$this->taskRepo = $taskRepo;
$this->invoiceRepo = $invoiceRepo;
}
/**
@ -71,8 +59,8 @@ class TaskController extends BaseController
->addColumn('client_name', function ($model) { return $model->client_public_id ? link_to('clients/'.$model->client_public_id, Utils::getClientDisplayName($model)) : ''; });
}
return $table->addColumn('start_time', function($model) { return Utils::fromSqlDateTime($model->start_time); })
->addColumn('duration', function($model) { return gmdate('H:i:s', $model->is_running ? time() - strtotime($model->start_time) : $model->duration); })
return $table->addColumn('created_at', function($model) { return Task::calcStartTime($model); })
->addColumn('time_log', function($model) { return gmdate('H:i:s', Task::calcDuration($model)); })
->addColumn('description', function($model) { return $model->description; })
->addColumn('invoice_number', function($model) { return self::getStatusLabel($model); })
->addColumn('dropdown', function ($model) {
@ -169,8 +157,16 @@ class TaskController extends BaseController
if ($task->invoice) {
$actions[] = ['url' => URL::to("inovices/{$task->invoice->public_id}/edit"), 'label' => trans("texts.view_invoice")];
} else {
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.invoice_task")];
$actions[] = ['url' => 'javascript:submitAction("invoice")', 'label' => trans("texts.create_invoice")];
// check for any open invoices
$invoices = $task->client_id ? $this->invoiceRepo->findOpenInvoices($task->client_id) : [];
foreach ($invoices as $invoice) {
$actions[] = ['url' => 'javascript:submitAction("add_to_invoice", '.$invoice->public_id.')', 'label' => trans("texts.add_to_invoice", ["invoice" => $invoice->invoice_number])];
}
}
$actions[] = DropdownButton::DIVIDER;
if (!$task->trashed()) {
$actions[] = ['url' => 'javascript:submitAction("archive")', 'label' => trans('texts.archive_task')];
@ -178,15 +174,15 @@ class TaskController extends BaseController
} else {
$actions[] = ['url' => 'javascript:submitAction("restore")', 'label' => trans('texts.restore_task')];
}
$data = [
'task' => $task,
'clientPublicId' => $task->client ? $task->client->public_id : 0,
'method' => 'PUT',
'url' => 'tasks/'.$publicId,
'title' => trans('texts.edit_task'),
'duration' => $task->resume_time ? ($task->duration + strtotime('now') - strtotime($task->resume_time)) : (strtotime('now') - strtotime($task->start_time)),
'actions' => $actions
'duration' => $task->is_running ? $task->getCurrentDuration() : $task->getDuration(),
'actions' => $actions,
];
$data = array_merge($data, self::getViewModel());
@ -216,7 +212,7 @@ class TaskController extends BaseController
{
$action = Input::get('action');
if (in_array($action, ['archive', 'delete', 'invoice', 'restore'])) {
if (in_array($action, ['archive', 'delete', 'invoice', 'restore', 'add_to_invoice'])) {
return self::bulk();
}
@ -235,12 +231,11 @@ class TaskController extends BaseController
$this->taskRepo->save($ids, ['action' => $action]);
Session::flash('message', trans('texts.stopped_task'));
return Redirect::to('tasks');
} else if ($action == 'invoice') {
} else if ($action == 'invoice' || $action == 'add_to_invoice') {
$tasks = Task::scope($ids)->with('client')->get();
$clientPublicId = false;
$data = [];
foreach ($tasks as $task) {
if ($task->client) {
if (!$clientPublicId) {
@ -258,16 +253,21 @@ class TaskController extends BaseController
Session::flash('error', trans('texts.task_error_invoiced'));
return Redirect::to('tasks');
}
$data[] = [
'publicId' => $task->public_id,
'description' => $task->description,
'startTime' => Utils::fromSqlDateTime($task->start_time),
'duration' => round($task->duration / (60 * 60), 2)
'startTime' => $task->getStartTime(),
'duration' => $task->getHours(),
];
}
return Redirect::to("invoices/create/{$clientPublicId}")->with('tasks', $data);
if ($action == 'invoice') {
return Redirect::to("invoices/create/{$clientPublicId}")->with('tasks', $data);
} else {
$invoiceId = Input::get('invoice_id');
return Redirect::to("invoices/{$invoiceId}/edit")->with('tasks', $data);
}
} else {
$count = $this->taskRepo->bulk($ids, $action);

View File

@ -1,5 +1,6 @@
<?php
/*
|--------------------------------------------------------------------------
| Application Routes

View File

@ -171,7 +171,7 @@ class Account extends Eloquent
{
$counter = $isQuote && !$this->share_counter ? $this->quote_number_counter : $this->invoice_number_counter;
$prefix .= $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
// confirm the invoice number isn't already taken
do {
$number = $prefix.str_pad($counter, 4, "0", STR_PAD_LEFT);
@ -186,11 +186,14 @@ class Account extends Eloquent
{
// check if the user modified the invoice number
if (!$isRecurring && $invoiceNumber != $this->getNextInvoiceNumber($isQuote)) {
$number = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
// remove the prefix
$prefix = $isQuote ? $this->quote_number_prefix : $this->invoice_number_prefix;
$invoiceNumber = preg_replace('/^'.$prefix.'/', '', $invoiceNumber);
$invoiceNumber = intval(preg_replace('/[^0-9]/', '', $invoiceNumber));
if ($isQuote && !$this->share_counter) {
$this->quote_number_counter = $number + 1;
$this->quote_number_counter = $invoiceNumber + 1;
} else {
$this->invoice_number_counter = $number + 1;
$this->invoice_number_counter = $invoiceNumber + 1;
}
// otherwise, just increment the counter
} else {

View File

@ -252,8 +252,11 @@ class Invoice extends EntityModel
}
}
Invoice::created(function ($invoice) {
Invoice::creating(function ($invoice) {
$invoice->account->incrementCounter($invoice->invoice_number, $invoice->is_quote, $invoice->recurring_invoice_id);
});
Invoice::created(function ($invoice) {
Activity::createInvoice($invoice);
});
@ -267,4 +270,4 @@ Invoice::deleting(function ($invoice) {
Invoice::restoring(function ($invoice) {
Activity::restoreInvoice($invoice);
});
});

View File

@ -1,7 +1,7 @@
<?php namespace App\Models;
use DB;
use Utils;
use Illuminate\Database\Eloquent\SoftDeletes;
class Task extends EntityModel
@ -22,6 +22,66 @@ class Task extends EntityModel
{
return $this->belongsTo('App\Models\Client')->withTrashed();
}
public static function calcStartTime($task)
{
$parts = json_decode($task->time_log) ?: [];
if (count($parts)) {
return Utils::timestampToDateTimeString($parts[0][0]);
} else {
return '';
}
}
public function getStartTime()
{
return self::calcStartTime($this);
}
public static function calcDuration($task)
{
$duration = 0;
$parts = json_decode($task->time_log) ?: [];
foreach ($parts as $part) {
if (count($part) == 1 || !$part[1]) {
$duration += time() - $part[0];
} else {
$duration += $part[1] - $part[0];
}
}
return $duration;
}
public function getDuration()
{
return self::calcDuration($this);
}
public function getCurrentDuration()
{
$parts = json_decode($this->time_log) ?: [];
$part = $parts[count($parts)-1];
if (count($part) == 1 || !$part[1]) {
return time() - $part[0];
} else {
return 0;
}
}
public function hasPreviousDuration()
{
$parts = json_decode($this->time_log) ?: [];
return count($parts) && (count($parts[0]) && $parts[0][1]);
}
public function getHours()
{
return round($this->getDuration() / (60 * 60), 2);
}
}
Task::created(function ($task) {

View File

@ -34,10 +34,14 @@ class Mailer
});
return true;
} catch (Exception $e) {
$response = $e->getResponse()->getBody()->getContents();
$response = json_decode($response);
return nl2br($response->Message);
} catch (Exception $exception) {
if (method_exists($exception, 'getResponse')) {
$response = $exception->getResponse()->getBody()->getContents();
$response = json_decode($response);
return nl2br($response->Message);
} else {
return $exception->getMessage();
}
}
}
}

View File

@ -536,4 +536,16 @@ class InvoiceRepository
return count($invoices);
}
public function findOpenInvoices($clientId)
{
return Invoice::scope()
->whereClientId($clientId)
->whereIsQuote(false)
->whereIsRecurring(false)
->whereHasTasks(true)
->where('balance', '>', 0)
->select(['public_id', 'invoice_number'])
->get();
}
}

View File

@ -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', 'tasks.is_running');
->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.description', 'tasks.is_deleted', 'tasks.deleted_at', 'invoices.invoice_number', 'invoices.public_id as invoice_public_id', 'tasks.is_running', 'tasks.time_log', 'tasks.created_at');
if ($clientPublicId) {
$query->where('clients.public_id', '=', $clientPublicId);
@ -60,36 +60,20 @@ class TaskRepository
$task->description = trim($data['description']);
}
$timeLog = $task->time_log ? json_decode($task->time_log, true) : [];
//$timeLog = $task->time_log ? json_decode($task->time_log, true) : [];
$timeLog = isset($data['time_log']) ? json_decode($data['time_log']) : [];
if ($data['action'] == 'start') {
$task->start_time = Carbon::now()->toDateTimeString();
$task->is_running = true;
$timeLog[] = [strtotime('now'), false];
} 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;
$timeLog[] = [strtotime('now'), false];
} 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);
}
$timeLog[count($timeLog)-1][1] = strtotime('now');
$timeLog[count($timeLog)-1][1] = 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, 0);
$task->break_duration = max($task->break_duration, 0);
$task->time_log = json_encode($timeLog);
$task->save();
return $task;

View File

@ -0,0 +1,73 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class SimplifyTasks extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tasks = \App\Models\Task::all();
foreach ($tasks as $task) {
$startTime = strtotime($task->start_time);
if (!$task->time_log || !count(json_decode($task->time_log))) {
$task->time_log = json_encode([[$startTime, $startTime + $task->duration]]);
$task->save();
} elseif ($task->getDuration() != intval($task->duration)) {
$task->time_log = json_encode([[$startTime, $startTime + $task->duration]]);
$task->save();
}
}
Schema::table('tasks', function($table)
{
$table->dropColumn('start_time');
$table->dropColumn('duration');
$table->dropColumn('break_duration');
$table->dropColumn('resume_time');
});
Schema::table('users', function($table)
{
$table->boolean('dark_mode')->default(false)->nullable();
});
Schema::table('users', function($table)
{
$table->dropColumn('theme_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tasks', function($table)
{
$table->timestamp('start_time')->nullable();
$table->integer('duration')->nullable();
$table->timestamp('resume_time')->nullable();
$table->integer('break_duration')->nullable();
});
Schema::table('users', function($table)
{
$table->dropColumn('dark_mode');
});
Schema::table('users', function($table)
{
$table->integer('theme_id')->nullable();
});
}
}

View File

@ -2433,6 +2433,8 @@ th {border-left: 1px solid #d26b26; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle;
border-top: none;
}
table.invoice-table>thead>tr>th, table.invoice-table>tbody>tr>th, table.invoice-table>tfoot>tr>th, table.invoice-table>thead>tr>td, table.invoice-table>tbody>tr>td, table.invoice-table>tfoot>tr>td {
border-bottom: 1px solid #dfe0e1;
}
table.dataTable.no-footer {

View File

@ -83,6 +83,8 @@ th {border-left: 1px solid #d26b26; }
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
vertical-align: middle;
border-top: none;
}
table.invoice-table>thead>tr>th, table.invoice-table>tbody>tr>th, table.invoice-table>tfoot>tr>th, table.invoice-table>thead>tr>td, table.invoice-table>tbody>tr>td, table.invoice-table>tfoot>tr>td {
border-bottom: 1px solid #dfe0e1;
}
table.dataTable.no-footer {

View File

@ -706,6 +706,24 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',

View File

@ -697,5 +697,24 @@ return array(
'login' => 'Login',
'or' => 'oder',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -276,9 +276,9 @@ return array(
// Payment page
'secure_payment' => 'Secure Payment',
'card_number' => 'Card number',
'expiration_month' => 'Expiration month',
'expiration_year' => 'Expiration year',
'card_number' => 'Card Number',
'expiration_month' => 'Expiration Month',
'expiration_year' => 'Expiration Year',
'cvv' => 'CVV',
// Security alerts
@ -526,11 +526,11 @@ return array(
'token_billing_secure' => 'The data is stored securely by :stripe_link',
'support' => 'Support',
'contact_information' => 'Contact information',
'contact_information' => 'Contact Information',
'256_encryption' => '256-Bit Encryption',
'amount_due' => 'Amount due',
'billing_address' => 'Billing address',
'billing_method' => 'Billing method',
'billing_address' => 'Billing Address',
'billing_method' => 'Billing Method',
'order_overview' => 'Order overview',
'match_address' => '*Address must match address associated with credit card.',
'click_once' => '*Please click "PAY NOW" only once - transaction may take up to 1 minute to process.',
@ -711,5 +711,16 @@ return array(
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -676,5 +676,24 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -705,6 +705,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -697,6 +697,25 @@ return array(
'login' => 'Connexion',
'or' => 'ou',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -698,5 +698,24 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -700,6 +700,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -707,6 +707,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -705,6 +705,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -700,6 +700,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -700,5 +700,24 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -703,6 +703,25 @@ return array(
'login' => 'Login',
'or' => 'or',
'email_error' => 'There was a problem sending the email',
'created_by_recurring' => 'Created by recurring invoice :invoice',
'confirm_recurring_timing' => 'Note: emails are sent at the start of the hour.',
'old_browser' => 'Please use a <a href="'.OUTDATE_BROWSER_URL.'" target="_blank">newer browser</a>',
'payment_terms_help' => 'Sets the default invoice due date',
'unlink_account' => 'Unlink Account',
'unlink' => 'Unlink',
'show_address' => 'Show Address',
'show_address_help' => 'Require client to provide their billing address',
'update_address' => 'Update Address',
'update_address_help' => 'Update client\'s address with provided details',
'times' => 'Times',
'set_now' => 'Set now',
'dark_mode' => 'Dark Mode',
'dark_mode_help' => 'Show white text on black background',
'add_to_invoice' => 'Add to invoice :invoice',
'create_new_invoice' => 'Create new invoice',
'task_errors' => 'Please correct any overlapping times',
);

View File

@ -14,9 +14,12 @@
<div class="panel-body">
@if ($accountGateway)
{!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! Former::populateField('gateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('payment_type_id', $paymentTypeId) !!}
{!! Former::populateField('recommendedGateway_id', $accountGateway->gateway_id) !!}
{!! Former::populateField('show_address', intval($accountGateway->show_address)) !!}
{!! Former::populateField('update_address', intval($accountGateway->update_address)) !!}
@if ($config)
@foreach ($accountGateway->fields as $field => $junk)
@if (in_array($field, $hiddenFields))
@ -28,6 +31,8 @@
@endif
@else
{!! Former::populateField('gateway_id', GATEWAY_STRIPE) !!}
{!! Former::populateField('show_address', 1) !!}
{!! Former::populateField('update_address', 1) !!}
@endif
{!! Former::select('payment_type_id')
@ -77,6 +82,15 @@
@endforeach
{!! Former::checkbox('show_address')
->label(trans('texts.billing_address'))
->text(trans('texts.show_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkbox('update_address')
->label(' ')
->text(trans('texts.update_address_help'))
->addGroupClass('gateway-option') !!}
{!! Former::checkboxes('creditCardTypes[]')
->label('Accepted Credit Cards')
->checkboxes($creditCardTypes)
@ -131,11 +145,25 @@
}
}
function enableUpdateAddress(event) {
var disabled = !$('#show_address').is(':checked');
$('#update_address').prop('disabled', disabled);
$('label[for=update_address]').css('color', disabled ? '#888' : '#000');
if (disabled) {
$('#update_address').prop('checked', false);
} else if (event) {
$('#update_address').prop('checked', true);
}
}
$(function() {
setPaymentType();
@if ($accountGateway)
$('.payment-type-option').hide();
@endif
$('#show_address').change(enableUpdateAddress);
enableUpdateAddress();
})
</script>

View File

@ -22,6 +22,9 @@
{{ Former::populateField('last_name', $account->users()->first()->last_name) }}
{{ Former::populateField('email', $account->users()->first()->email) }}
{{ Former::populateField('phone', $account->users()->first()->phone) }}
@if (Utils::isNinja())
{{ Former::populateField('dark_mode', intval($account->users()->first()->dark_mode)) }}
@endif
@endif
<div class="row">
@ -88,6 +91,10 @@
{!! Former::text('last_name') !!}
{!! Former::text('email') !!}
{!! Former::text('phone') !!}
@if (Utils::isNinja())
{!! Former::checkbox('dark_mode')->text(trans('texts.dark_mode_help')) !!}
@endif
@if (Auth::user()->confirmed)
{!! Former::actions( Button::primary(trans('texts.change_password'))->small()->withAttributes(['onclick'=>'showChangePassword()'])) !!}
@elseif (Auth::user()->registered)

View File

@ -23,8 +23,7 @@
{{ Former::populateField('custom_invoice_taxes2', intval($account->custom_invoice_taxes2)) }}
{{ Former::populateField('share_counter', intval($account->share_counter)) }}
{{ Former::populateField('pdf_email_attachment', intval($account->pdf_email_attachment)) }}
{{ Former::populateField('utf8_invoices', intval($account->utf8_invoices)) }}
{{ Former::populateField('auto_wrap', intval($account->auto_wrap)) }}
{{ Former::populateField('utf8_invoices', intval($account->utf8_invoices)) }}
<div class="row">
<div class="col-md-6">
@ -99,9 +98,6 @@
<div class="panel-body">
{!! Former::checkbox('pdf_email_attachment')->text(trans('texts.enable')) !!}
{!! Former::checkbox('utf8_invoices')->text(trans('texts.enable')) !!}
<div style="display:none">
{!! Former::checkbox('auto_wrap')->text(trans('texts.enable')) !!}
</div>
</div>
</div>
</div>

View File

@ -25,6 +25,23 @@
}
}
@if (Auth::check() && Auth::user()->dark_mode)
body {
background: #000 !important;
color: white !important;
}
.panel-body {
background: #272822 !important;
/*background: #e6e6e6 !important;*/
}
.panel-default {
border-color: #444;
}
@endif
</style>
@include('script')
@ -309,6 +326,15 @@
showSignUp();
@endif
$('ul.navbar-settings, ul.navbar-history').hover(function () {
//$('.user-accounts').find('li').hide();
//$('.user-accounts').css({display: 'none'});
//console.log($('.user-accounts').dropdown(''))
if ($('.user-accounts').css('display') == 'block') {
$('.user-accounts').dropdown('toggle');
}
});
@yield('onReady')
});
@ -409,7 +435,7 @@
</div>
<ul class="nav navbar-nav navbar-right">
<ul class="nav navbar-nav navbar-right navbar-settings">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-cog" title="{{ trans('texts.settings') }}"/>
@ -426,7 +452,7 @@
</ul>
<ul class="nav navbar-nav navbar-right">
<ul class="nav navbar-nav navbar-right navbar-history">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="glyphicon glyphicon-time" title="{{ trans('texts.history') }}"/>

View File

@ -646,6 +646,10 @@
setComboboxValue($('.client_select'),
client.public_id(),
client.name.display());
@if (isset($tasks) && $tasks)
NINJA.formIsChanged = true;
@endif
});
function applyComboboxListeners() {
@ -1755,23 +1759,24 @@
//}
model.invoice().custom_taxes1({{ $account->custom_invoice_taxes1 ? 'true' : 'false' }});
model.invoice().custom_taxes2({{ $account->custom_invoice_taxes2 ? 'true' : 'false' }});
@if (isset($tasks) && $tasks)
// move the blank invoice line item to the end
var blank = model.invoice().invoice_items.pop();
var tasks = {!! $tasks !!};
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
var item = model.invoice().addItem();
item.notes(task.description);
item.product_key(task.startTime);
item.qty(task.duration);
item.task_public_id(task.publicId);
}
model.invoice().invoice_items.push(blank);
model.invoice().has_tasks(true);
@endif
@endif
@if (isset($tasks) && $tasks)
// move the blank invoice line item to the end
var blank = model.invoice().invoice_items.pop();
var tasks = {!! $tasks !!};
console.log(tasks);
for (var i=0; i<tasks.length; i++) {
var task = tasks[i];
var item = model.invoice().addItem();
item.notes(task.description);
item.product_key(task.startTime);
item.qty(task.duration);
item.task_public_id(task.publicId);
}
model.invoice().invoice_items.push(blank);
model.invoice().has_tasks(true);
@endif
@endif
model.invoice().tax(model.getTaxRate(model.invoice().tax_name(), model.invoice().tax_rate()));

View File

@ -207,6 +207,7 @@ header h3 em {
<p>&nbsp;<br/>&nbsp;</p>
@if ($showAddress)
<h3>{{ trans('texts.billing_address') }} &nbsp;<span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
<div class="row">
<div class="col-md-6">
@ -234,6 +235,7 @@ header h3 em {
</div>
<p>&nbsp;<br/>&nbsp;</p>
@endif
<h3>{{ trans('texts.billing_method') }}</h3>
<div class="row">

View File

@ -4,10 +4,6 @@
<style type="text/css">
.date-group div.input-group {
width: 250px;
}
.time-input input,
.time-input select {
float: left;
@ -16,27 +12,23 @@
</style>
{!! Former::open($url)->addClass('col-md-10 col-md-offset-1 warn-on-exit task-form')->method($method)->rules(array(
)) !!}
{!! Former::open($url)->addClass('col-md-10 col-md-offset-1 warn-on-exit task-form')->method($method)->rules(array()) !!}
@if ($task)
{!! Former::populate($task) !!}
{!! Former::populateField('id', $task->public_id) !!}
@endif
<div style="display:none">
@if ($task)
{!! Former::text('id') !!}
{!! Former::populateField('id', $task->public_id) !!}
{!! Former::text('invoice_id') !!}
@endif
{!! Former::text('action') !!}
{!! Former::text('start_time') !!}
{!! Former::text('duration') !!}
{!! Former::text('break_duration') !!}
{!! Former::text('time_log') !!}
</div>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
@ -44,98 +36,75 @@
{!! Former::select('client')->addOption('', '')->addGroupClass('client-select') !!}
{!! Former::textarea('description')->rows(3) !!}
@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
<div class="form-group simple-time" id="editDetailsLink">
<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">
<p>{{ $task->getStartTime() }}<p/>
@if ($task->hasPreviousDuration())
{{ trans('texts.duration') . ': ' . gmdate('H:i:s', $task->getDuration()) }}<br/>
@endif
@if (!$task->is_running)
<p>{!! Button::primary(trans('texts.edit_details'))->withAttributes(['onclick'=>'showTimeDetails()'])->small() !!}</p>
</div>
@endif
</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('&nbsp;') !!}
@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') !!}
<div class="form-group">
<label for="time" class="control-label col-lg-4 col-sm-4">
{{ trans('texts.time') }}
</label>
<div class="col-lg-8 col-sm-8 time-input">
<input class="form-control" id="start_hours" placeholder="{{ uctrans('texts.hours') }}"
name="value" size="3" type="number" min="1" max="12" step="1"/>
<input class="form-control" id="start_minutes" placeholder="{{ uctrans('texts.minutes') }}"
name="value" size="2" type="number" min="0" max="59" step="1"/>
<input class="form-control" id="start_seconds" placeholder="{{ uctrans('texts.seconds') }}"
name="value" size="2" type="number" min="0" max="59" step="1"/>
<select class="form-control" id="start_ampm">
<option>AM</option>
<option>PM</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-lg-4 col-sm-4">
{{ trans('texts.work') }}
</label>
<div class="col-lg-8 col-sm-8 time-input">
<input class="form-control" id="duration_hours" placeholder="{{ uctrans('texts.hours') }}"
name="value" size="3" type="number" min="0" step="1"/>
<input class="form-control" id="duration_minutes" placeholder="{{ uctrans('texts.minutes') }}"
name="value" size="2" type="number" min="0" max="59" step="1"/>
<input class="form-control" id="duration_seconds" placeholder="{{ uctrans('texts.seconds') }}"
name="value" size="2" type="number" min="0" max="59" step="1"/>
</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') }}
</label>
<div class="col-lg-8 col-sm-8" style="padding-top: 10px">
</div>
</div>
</div>
@if ($task->is_running)
<center>
<div id="duration-text" style="font-size: 36px; font-weight: 300; padding: 30px 0 20px 0"/>
</center>
@endif
@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('&nbsp;') !!}
@endif
<div class="form-group simple-time" id="datetime-details" style="display: none">
<label for="simple-time" class="control-label col-lg-4 col-sm-4">
{{ trans('texts.times') }}
</label>
<div class="col-lg-8 col-sm-8">
<table class="table" style="margin-bottom: 0px !important;">
<tbody data-bind="foreach: $root.time_log">
<tr data-bind="event: { mouseover: showActions, mouseout: hideActions }">
<td style="padding: 0px 12px 12px 0 !important">
<div data-bind="css: { 'has-error': !isStartValid() }">
<input type="text" data-bind="value: startTime.pretty, event:{ change: $root.refresh }"
class="form-control" placeholder="{{ trans('texts.start_time') }}"/>
</div>
</td>
<td style="padding: 0px 12px 12px 0 !important">
<div data-bind="css: { 'has-error': !isEndValid() }">
<input type="text" data-bind="value: endTime.pretty, event:{ change: $root.refresh }"
class="form-control" placeholder="{{ trans('texts.end_time') }}"/>
</div>
</td>
<td style="width:100px">
<div data-bind="text: duration.pretty, visible: !isEmpty()"></div>
<a href="#" data-bind="click: function() { setNow(), $root.refresh() }, visible: isEmpty()">{{ trans('texts.set_now') }}</a>
</td>
<td style="width:30px" class="td-icon">
<i style="width:12px;cursor:pointer" data-bind="click: $root.removeItem, visible: actionsVisible() &amp;&amp; !isEmpty()" class="fa fa-minus-circle redlink" title="Remove item"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -199,27 +168,25 @@
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);
$('#duration').val(duration);
function submitAction(action, invoice_id) {
model.refresh();
var data = [];
for (var i=0; i<model.time_log().length; i++) {
var timeLog = model.time_log()[i];
if (!timeLog.isEmpty()) {
data.push([timeLog.startTime(),timeLog.endTime()]);
}
@if ($task && !$task->is_running)
if (!timeLog.isStartValid() || !timeLog.isEndValid()) {
alert("{{ trans('texts.task_errors') }}");
showTimeDetails();
return;
}
@endif
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());
}
function submitAction(action) {
}
$('#invoice_id').val(invoice_id);
$('#time_log').val(JSON.stringify(data));
$('#action').val(action);
$('.task-form').submit();
}
@ -235,12 +202,139 @@
$('#editDetailsLink').hide();
}
function TimeModel(data) {
console.log('== TimeModel ==');
console.log(data);
var self = this;
self.startTime = ko.observable(0);
self.endTime = ko.observable(0);
self.duration = ko.observable(0);
self.actionsVisible = ko.observable(false);
self.isStartValid = ko.observable(true);
self.isEndValid = ko.observable(true);
if (data) {
self.startTime(data[0]);
self.endTime(data[1]);
};
self.isEmpty = ko.computed(function() {
return !self.startTime() && !self.endTime();
});
self.startTime.pretty = ko.computed({
read: function() {
return self.startTime() ? moment.unix(self.startTime()).format('MMM D YYYY h:mm:ss a') : '';
},
write: function(data) {
self.startTime(moment(data, 'MMM D YYYY h:mm:ss a').unix());
}
});
self.endTime.pretty = ko.computed({
read: function() {
return self.endTime() ? moment.unix(self.endTime()).format('MMM D YYYY h:mm:ss a') : '';
},
write: function(data) {
self.endTime(moment(data, 'MMM D YYYY h:mm:ss a').unix());
}
});
self.setNow = function() {
self.startTime(moment().unix());
self.endTime(moment().unix());
}
self.duration.pretty = ko.computed(function() {
var duration = false;
var start = self.startTime();
var end = self.endTime();
if (start && end) {
var duration = end - start;
}
var duration = moment.duration(duration * 1000);
return Math.floor(duration.asHours()) + moment.utc(duration.asMilliseconds()).format(":mm:ss")
}, self);
/*
self.isEmpty = function() {
return false;
};
*/
self.hideActions = function() {
self.actionsVisible(false);
};
self.showActions = function() {
self.actionsVisible(true);
};
}
function ViewModel(data) {
console.log('== ViewModel ==');
console.log(data);
var self = this;
self.time_log = ko.observableArray();
if (data) {
data = JSON.parse(data.time_log);
console.log(data);
for (var i=0; i<data.length; i++) {
self.time_log.push(new TimeModel(data[i]));
}
}
self.time_log.push(new TimeModel());
self.removeItem = function(item) {
self.time_log.remove(item);
self.refresh();
}
self.refresh = function() {
var hasEmpty = false;
var lastTime = 0;
for (var i=0; i<self.time_log().length; i++) {
var timeLog = self.time_log()[i];
var startValid = true;
var endValid = true;
if (timeLog.isEmpty()) {
hasEmpty = true;
} else {
if (timeLog.startTime() < lastTime || timeLog.startTime() > timeLog.endTime()) {
startValid = false;
}
if (timeLog.endTime() < Math.min(timeLog.startTime(), lastTime)) {
endValid = false;
}
lastTime = Math.max(lastTime, timeLog.endTime());
}
timeLog.isStartValid(startValid);
timeLog.isEndValid(endValid);
}
if (!hasEmpty) {
self.addItem();
}
}
self.addItem = function() {
self.time_log.push(new TimeModel());
}
}
window.model = new ViewModel({!! $task !!});
ko.applyBindings(model);
$(function() {
var $clientSelect = $('select#client');
for (var i=0; i<clients.length; i++) {
var client = clients[i];
$clientSelect.append(new Option(getClientDisplayName(client), client.public_id));
}
}
if ({{ $clientPublicId ? 'true' : 'false' }}) {
$clientSelect.val({{ $clientPublicId }});
@ -253,10 +347,6 @@
@else
var date = new Date();
$('#date').datepicker('update', date);
$('#start_hours').val((date.getHours() % 12) || 12);
$('#start_minutes').val(date.getMinutes());
$('#start_seconds').val(date.getSeconds());
$('#start_ampm').val(date.getHours() >= 12 ? 'PM' : 'AM');
@endif
@if (!$task && !$clientPublicId)
@ -272,8 +362,8 @@
} else {
$('#datetime-details').fadeIn();
}
$('#start-button').toggle();
$('#save-button').toggle();
$('#start-button').toggle();
$('#save-button').toggle();
})
$('#start-button').click(function() {
@ -289,49 +379,11 @@
submitAction('resume');
});
$('.time-input').on('keyup change', (function() {
determineEndTime();
}));
@if ($task)
NINJA.startTime = {{ strtotime($task->start_time) }};
@if ($task->is_running)
tock({{ $duration }});
@else
var date = new Date(NINJA.startTime * 1000);
var hours = date.getHours();
var pm = false;
if (hours >= 12) {
pm = true;
if (hours > 12) {
hours -= 12;
}
}
if (!hours) {
hours = 12;
}
$('#start_hours').val(hours);
$('#start_minutes').val(twoDigits(date.getMinutes()));
$('#start_seconds').val(twoDigits(date.getSeconds()));
$('#start_ampm').val(pm ? 'PM' : 'AM');
var parts = secondsToTime({{ $task->duration }});
$('#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
determineEndTime();
});
</script>