First attempt at basic tag functionality.

This commit is contained in:
James Cole 2015-04-28 08:58:01 +02:00
parent 6081cc399f
commit e7165a526b
26 changed files with 1046 additions and 67 deletions

View File

@ -0,0 +1,152 @@
<?php
namespace FireflyIII\Http\Controllers;
use Auth;
use Carbon\Carbon;
use FireflyIII\Http\Requests\TagFormRequest;
use FireflyIII\Models\Preference;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Input;
use Preferences;
use Response;
use Session;
use View;
use URL;
use Redirect;
/**
* Class TagController
*
* @package FireflyIII\Http\Controllers
*/
class TagController extends Controller
{
/**
*
*/
public function __construct()
{
View::share('title', 'Tags');
View::share('mainTitleIcon', 'fa-tags');
$tagOptions = [
'nothing' => 'Just a regular tag.',
'balancingAct' => 'The tag takes at most two transactions; an expense and a transfer. They\'ll balance each other out.',
'advancePayment' => 'The tag accepts one expense and any number of deposits aimed to repay the original expense.',
];
View::share('tagOptions', $tagOptions);
}
/**
* @return \Illuminate\View\View
*/
public function create()
{
$subTitle = 'New tag';
$subTitleIcon = 'fa-tag';
$preFilled = [
'tagMode' => 'nothing'
];
if (!Input::old('tagMode')) {
Session::flash('preFilled', $preFilled);
}
// put previous url in session if not redirect from store (not "create another").
if (Session::get('tags.create.fromStore') !== true) {
Session::put('tags.create.url', URL::previous());
}
Session::forget('tags.create.fromStore');
return view('tags.create', compact('subTitle', 'subTitleIcon'));
}
public function edit(Tag $tag)
{
$subTitle = 'Edit tag "' . e($tag->tag) . '"';
$subTitleIcon = 'fa-tag';
return view('tags.edit', compact('tag', 'subTitle', 'subTitleIcon'));
}
/**
* @param $state
*/
public function hideTagHelp($state)
{
$state = $state == 'true' ? true : false;
Preferences::set('hideTagHelp', $state);
return Response::json(true);
}
/**
*
*/
public function index()
{
/** @var Preference $helpHiddenPref */
$helpHiddenPref = Preferences::get('hideTagHelp', false);
$title = 'Tags';
$mainTitleIcon = 'fa-tags';
$helpHidden = $helpHiddenPref->data;
$tags = Auth::user()->tags()->get();
return view('tags.index', compact('title', 'mainTitleIcon', 'helpHidden', 'tags'));
}
/**
* @param Tag $tag
*
* @return \Illuminate\View\View
*/
public function show(Tag $tag)
{
$subTitle = $tag->tag;
$subTitleIcon = 'fa-tag';
return view('tags.show', compact('tag', 'subTitle', 'subTitleIcon'));
}
/**
* @param TagFormRequest $request
*/
public function store(TagFormRequest $request, TagRepositoryInterface $repository)
{
if (Input::get('setTag') == 'true') {
$latitude = strlen($request->get('latitude')) > 0 ? $request->get('latitude') : null;
$longitude = strlen($request->get('longitude')) > 0 ? $request->get('longitude') : null;
$zoomLevel = strlen($request->get('zoomLevel')) > 0 ? $request->get('zoomLevel') : null;
} else {
$latitude = null;
$longitude = null;
$zoomLevel = null;
}
$data = [
'tag' => $request->get('tag'),
'date' => strlen($request->get('date')) > 0 ? new Carbon($request->get('date')) : null,
'description' => strlen($request->get('description')) > 0 ? $request->get('description') : null,
'latitude' => $latitude,
'longitude' => $longitude,
'zoomLevel' => $zoomLevel,
'tagMode' => $request->get('tagMode'),
];
$tag = $repository->store($data);
Session::flash('success','The tag has been created!');
if (intval(Input::get('create_another')) === 1) {
// set value so create routine will not overwrite URL:
Session::put('tags.create.fromStore', true);
return Redirect::route('tags.create')->withInput();
}
// redirect to previous URL.
return Redirect::to(Session::get('tags.create.url'));
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 27/04/15
* Time: 12:50
*/
namespace FireflyIII\Http\Requests;
use Auth;
/**
* Class TagFormRequest
*
* @package FireflyIII\Http\Requests
*/
class TagFormRequest extends Request
{
/**
* @return bool
*/
public function authorize()
{
// Only allow logged in users
return Auth::check();
}
/**
* @return array
*/
public function rules()
{
return [
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag,TRUE',
'description' => 'min:1',
'date' => 'date',
'latitude' => 'numeric|min:-90|max:90',
'longitude' => 'numeric|min:-90|max:90',
'tagMode' => 'required|in:nothing,balancingAct,advancePayment'
];
}
}

View File

@ -10,7 +10,7 @@ use FireflyIII\Models\LimitRepetition;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Reminder;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Tag;
/*
* Back home.
*/
@ -350,3 +350,24 @@ Breadcrumbs::register(
}
);
// tags
Breadcrumbs::register(
'tags.index', function (Generator $breadcrumbs) {
$breadcrumbs->parent('home');
$breadcrumbs->push('Tags', route('tags.index'));
}
);
Breadcrumbs::register(
'tags.create', function (Generator $breadcrumbs) {
$breadcrumbs->parent('tags.index');
$breadcrumbs->push('Create tag', route('tags.create'));
}
);
Breadcrumbs::register(
'tags.show', function (Generator $breadcrumbs, Tag $tag) {
$breadcrumbs->parent('tags.index');
$breadcrumbs->push(e($tag->tag), route('tags.show', $tag));
}
);

View File

@ -8,6 +8,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\Reminder;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\Tag;
// models
@ -124,6 +125,16 @@ Route::bind(
}
);
Route::bind(
'tag', function ($value, $route) {
if (Auth::check()) {
return Tag::where('id', $value)->where('user_id', Auth::user()->id)->first();
}
return null;
}
);
/**
* Auth\AuthController
@ -327,6 +338,22 @@ Route::group(
*/
Route::get('/search', ['uses' => 'SearchController@index', 'as' => 'search']);
/**
* Tag Controller
*/
Route::get('/tags', ['uses' => 'TagController@index', 'as' => 'tags.index']);
Route::get('/tags/create', ['uses' => 'TagController@create', 'as' => 'tags.create']);
Route::get('/tags/show/{tag}', ['uses' => 'TagController@show', 'as' => 'tags.show']);
Route::get('/tags/edit/{tag}', ['uses' => 'TagController@edit', 'as' => 'tags.edit']);
Route::get('/tags/delete/{tag}', ['uses' => 'TagController@delete', 'as' => 'tags.delete']);
Route::post('/tags/store', ['uses' => 'TagController@store', 'as' => 'tags.store']);
Route::post('/tags/update/{tag}', ['uses' => 'TagController@update', 'as' => 'tags.update']);
Route::post('/tags/destroy/{tag}', ['uses' => 'TagController@destroy', 'as' => 'tags.destroy']);
Route::post('/tags/hideTagHelp/{state}', ['uses' => 'TagController@hideTagHelp', 'as' => 'tags.hideTagHelp']);
/**
* Transaction Controller
*/

77
app/Models/Tag.php Normal file
View File

@ -0,0 +1,77 @@
<?php
namespace FireflyIII\Models;
use Crypt;
use Illuminate\Database\Eloquent\Model;
/**
* Class Tag
*
* @package FireflyIII\Models
*/
class Tag extends Model
{
protected $fillable = ['user_id', 'tag', 'date', 'description', 'longitude', 'latitude','zoomLevel','tagMode'];
/**
* @return array
*/
public function getDates()
{
return ['created_at', 'updated_at', 'date'];
}
/**
* @param $value
*
* @return string
*/
public function getDescriptionAttribute($value)
{
return Crypt::decrypt($value);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function transactionjournals()
{
return $this->belongsToMany('FireflyIII\Models\TransactionJournal');
}
/**
* @param $value
*
* @return string
*/
public function getTagAttribute($value)
{
return Crypt::decrypt($value);
}
/**
* @param $value
*/
public function setDescriptionAttribute($value)
{
$this->attributes['description'] = Crypt::encrypt($value);
}
/**
* @param $value
*/
public function setTagAttribute($value)
{
$this->attributes['tag'] = Crypt::encrypt($value);
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('FireflyIII\User');
}
}

View File

@ -241,4 +241,12 @@ class TransactionJournal extends Model
return $this->belongsTo('FireflyIII\User');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tags()
{
return $this->belongsToMany('FireflyIII\Models\Tag');
}
}

View File

@ -63,6 +63,7 @@ class FireflyServiceProvider extends ServiceProvider
$this->app->bind('FireflyIII\Repositories\Bill\BillRepositoryInterface', 'FireflyIII\Repositories\Bill\BillRepository');
$this->app->bind('FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface', 'FireflyIII\Repositories\PiggyBank\PiggyBankRepository');
$this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository');
$this->app->bind('FireflyIII\Repositories\Tag\TagRepositoryInterface', 'FireflyIII\Repositories\Tag\TagRepository');
$this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search');

View File

@ -0,0 +1,39 @@
<?php
namespace FireflyIII\Repositories\Tag;
use Auth;
use FireflyIII\Models\Tag;
/**
* Class TagRepository
*
* @package FireflyIII\Repositories\Tag
*/
class TagRepository implements TagRepositoryInterface
{
/**
* @param array $data
*
* @return Tag
*/
public function store(array $data)
{
$tag = new Tag;
$tag->tag = $data['tag'];
$tag->date = $data['date'];
$tag->description = $data['description'];
$tag->latitude = $data['latitude'];
$tag->longitude = $data['longitude'];
$tag->zoomLevel = $data['zoomLevel'];
$tag->tagMode = $data['tagMode'];
$tag->user()->associate(Auth::user());
$tag->save();
return $tag;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace FireflyIII\Repositories\Tag;
use FireflyIII\Models\Tag;
/**
* Interface TagRepositoryInterface
*
* @package FireflyIII\Repositories\Tag
*/
interface TagRepositoryInterface {
/**
* @param array $data
*
* @return Tag
*/
public function store(array $data);
}

View File

@ -62,7 +62,10 @@ class ExpandedForm
'account_id' => 'Asset account',
'budget_id' => 'Budget',
'openingBalance' => 'Opening balance',
'tagMode' => 'Tag mode',
'tagPosition' => 'Tag location',
'virtualBalance' => 'Virtual balance',
'longitude_latitude' => 'Location',
'targetamount' => 'Target amount',
'accountRole' => 'Account role',
'openingBalanceDate' => 'Opening balance date',
@ -201,15 +204,17 @@ class ExpandedForm
*
* @return string
*/
public function month($name, $value = null, array $options = [])
public function integer($name, $value = null, array $options = [])
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$html = View::make('form.month', compact('classes', 'name', 'label', 'value', 'options'))->render();
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$options['step'] = '1';
$html = View::make('form.integer', compact('classes', 'name', 'label', 'value', 'options'))->render();
return $html;
}
/**
@ -219,14 +224,13 @@ class ExpandedForm
*
* @return string
*/
public function integer($name, $value = null, array $options = [])
public function location($name, $value = null, array $options = [])
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$options['step'] = '1';
$html = View::make('form.integer', compact('classes', 'name', 'label', 'value', 'options'))->render();
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$html = View::make('form.location', compact('classes', 'name', 'label', 'value', 'options'))->render();
return $html;
@ -265,6 +269,44 @@ class ExpandedForm
return $selectList;
}
/**
* @param $name
* @param null $value
* @param array $options
*
* @return string
*/
public function month($name, $value = null, array $options = [])
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$html = View::make('form.month', compact('classes', 'name', 'label', 'value', 'options'))->render();
return $html;
}
/**
* @param $name
* @param null $value
* @param array $options
*
* @return string
*/
public function multiRadio($name, array $list = [], $selected = null, array $options = [])
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$selected = $this->fillFieldValue($name, $selected);
unset($options['class']);
$html = View::make('form.multiRadio', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
return $html;
}
/**
* @param $type
* @param $name
@ -336,4 +378,24 @@ class ExpandedForm
return $html;
}
/**
* @param $name
* @param null $value
* @param array $options
*
* @return string
*/
public function textarea($name, $value = null, array $options = [])
{
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$value = $this->fillFieldValue($name, $value);
$options['rows'] = 4;
$html = View::make('form.textarea', compact('classes', 'name', 'label', 'value', 'options'))->render();
return $html;
}
}

View File

@ -43,6 +43,14 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
return $this->hasMany('FireflyIII\Models\Account');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tags()
{
return $this->hasMany('FireflyIII\Models\Tag');
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/

View File

@ -181,10 +181,14 @@ class FireflyValidator extends Validator
*/
public function validateUniqueObjectForUser($attribute, $value, $parameters)
{
$table = $parameters[0];
$field = $parameters[1];
$encrypted = isset($parameters[2]) ? $parameters[2] : 'encrypted';
$exclude = isset($parameters[3]) ? $parameters[3] : null;
$table = $parameters[0];
$field = $parameters[1];
$encrypted = isset($parameters[2]) ? $parameters[2] : 'encrypted';
$exclude = isset($parameters[3]) ? $parameters[3] : null;
$alwaysEncrypted = false;
if ($encrypted == 'TRUE') {
$alwaysEncrypted = true;
}
$query = DB::table($table)->where('user_id', Auth::user()->id);
@ -195,8 +199,12 @@ class FireflyValidator extends Validator
$set = $query->get();
foreach ($set as $entry) {
$isEncrypted = intval($entry->$encrypted) == 1 ? true : false;
$checkValue = $isEncrypted ? Crypt::decrypt($entry->$field) : $entry->$field;
if (!$alwaysEncrypted) {
$isEncrypted = intval($entry->$encrypted) == 1 ? true : false;
} else {
$isEncrypted = true;
}
$checkValue = $isEncrypted ? Crypt::decrypt($entry->$field) : $entry->$field;
if ($checkValue == $value) {
return false;
}

View File

@ -137,6 +137,9 @@ return [
'Illuminate\View\ViewServiceProvider',
'Illuminate\Html\HtmlServiceProvider',
'DaveJamesMiller\Breadcrumbs\ServiceProvider',
'Barryvdh\Debugbar\ServiceProvider',
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
/*
* Application Service Providers...

View File

@ -0,0 +1,87 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class ChangesForV3310 extends Migration
{
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tags');
Schema::drop('tag_transaction_journal');
}
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
//
Schema::table(
'transaction_groups', function (Blueprint $table) {
// drop column "relation"
$table->dropColumn('relation');
}
);
Schema::table(
'transaction_groups', function (Blueprint $table) {
// drop column "relation"
$table->string('relation', 50);
}
);
// make new column "relation"
// set all current entries to be "balance"
DB::table('transaction_groups')->update(['relation' => 'balance']);
/*
* New table!
*/
Schema::create(
'tags', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->softDeletes();
$table->integer('user_id')->unsigned();
$table->string('tag', 1024);
$table->string('tagMode', 1024);
$table->date('date')->nullable();
$table->text('description')->nullable();
$table->decimal('latitude', 18, 12)->nullable();
$table->decimal('longitude', 18, 12)->nullable();
$table->smallInteger('zoomLevel', false, true)->nullable();
// connect reminders to users
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
}
);
Schema::create('tag_transaction_journal',function (Blueprint $table) {
$table->increments('id');
$table->integer('tag_id')->unsigned();
$table->integer('transaction_journal_id')->unsigned();
// link to foreign tables.
$table->foreign('tag_id', 'tag_grp_id')->references('id')->on('tags')->onDelete('cascade');
$table->foreign('transaction_journal_id', 'tag_trj_id')->references('id')->on('transaction_journals')->onDelete('cascade');
// add unique.
$table->unique(['tag_id', 'transaction_journal_id'], 'tag_t_joined');
});
}
}

View File

@ -5,4 +5,9 @@
.ui-sortable-placeholder {
display: inline-block;
height: 1px;
}
#map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}

115
public/js/tags.js Normal file
View File

@ -0,0 +1,115 @@
$(function () {
/*
Hide and show the tag index help.
*/
$('#tagHelp').on('show.bs.collapse', function () {
// set hideTagHelp = false
$.post('/tags/hideTagHelp/false', {_token: token});
$('#tagHelpButton').text('Hide help');
}).on('hide.bs.collapse', function () {
// set hideTagHelp = true
$.post('/tags/hideTagHelp/true', {_token: token});
$('#tagHelpButton').text('Show help');
});
$('#clearLocation').click(clearLocation);
});
/*
Some vars as prep for the map:
*/
var map;
var markers = [];
var setTag = false;
var mapOptions = {
zoom: zoomLevel,
center: new google.maps.LatLng(latitude, longitude),
disableDefaultUI: true
};
/*
Clear location and reset zoomLevel.
*/
function clearLocation() {
"use strict";
deleteMarkers();
$('input[name="latitude"]').val("");
$('input[name="longitude"]').val("");
$('input[name="zoomLevel"]').val("6");
setTag = false;
$('input[name="setTag"]').val('false');
return false;
}
function initialize() {
/*
Create new map:
*/
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
/*
Respond to click event.
*/
google.maps.event.addListener(map, 'rightclick', function (event) {
placeMarker(event);
});
/*
Respond to zoom event.
*/
google.maps.event.addListener(map, 'zoom_changed', function () {
saveZoomLevel(event);
});
/*
Maybe place marker?
*/
if(doPlaceMarker) {
var myLatlng = new google.maps.LatLng(latitude,longitude);
var fakeEvent = {};
fakeEvent.latLng = myLatlng;
placeMarker(fakeEvent);
}
}
/**
* save zoom level of map into hidden input.
*/
function saveZoomLevel() {
"use strict";
$('input[name="zoomLevel"]').val(map.getZoom());
}
/**
* Place marker on map.
* @param event
*/
function placeMarker(event) {
deleteMarkers();
var marker = new google.maps.Marker({position: event.latLng, map: map});
$('input[name="latitude"]').val(event.latLng.lat());
$('input[name="longitude"]').val(event.latLng.lng());
markers.push(marker);
setTag = true;
$('input[name="setTag"]').val('true');
}
/**
* Deletes all markers in the array by removing references to them.
*/
function deleteMarkers() {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
markers = [];
}
google.maps.event.addDomListener(window, 'load', initialize);

View File

@ -0,0 +1,14 @@
<div class="{{{$classes}}}">
<label for="{{{$options['id']}}}" class="col-sm-4 control-label">{{{$label}}}</label>
<div class="col-sm-8">
<div id="map-canvas" style="width:100%;height:300px;"></div>
<p class="help-block">Right-click to set the tag's location.
<a href="#" id="clearLocation">Clear location</a>
</p>
<input type="hidden" name="latitude" value="" />
<input type="hidden" name="longitude" value="" />
<input type="hidden" name="zoomLevel" value="6" />
<input type="hidden" name="setTag" value="" />
@include('form.feedback')
</div>
</div>

View File

@ -0,0 +1,16 @@
<div class="{{{$classes}}}">
<label for="{{{$options['id']}}}" class="col-sm-4 control-label">{{{$label}}}</label>
<div class="col-sm-8">
@foreach($list as $value => $description)
<div class="radio">
<label>
{!! Form::radio($name, $value, ($selected == $value), $options) !!}
{{$description}}
</label>
</div>
@endforeach
@include('form.help')
@include('form.feedback')
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="{{{$classes}}}">
<label for="{{{$options['id']}}}" class="col-sm-4 control-label">{{{$label}}}</label>
<div class="col-sm-8">
{!! Form::textarea($name, $value, $options) !!}
@include('form.feedback')
</div>
</div>

View File

@ -1,5 +1,4 @@
@extends('app')
@extends('layouts.default')
@section('content')
<div class="container">
<div class="row">

View File

@ -122,7 +122,7 @@
<a @if($r == 'categories.index') class="active" @endif href="{{route('categories.index')}}"><i class="fa fa-bar-chart fa-fw"></i> Categories</a>
</li>
<li>
<a href="#"><i class="fa fa-tags fa-fw"></i> Tags</a>
<a @if($r == 'tags.index') class="active" @endif href="{{route('tags.index')}}"><i class="fa fa-tags fa-fw"></i> Tags</a>
</li>
<li>
<a @if(!(strpos($r,'reports') === false)) class="active" @endif href="{{route('reports.index')}}"><i class="fa fa-line-chart fa-fw"></i> Reports</a>

View File

@ -0,0 +1,85 @@
@extends('layouts.default')
@section('content')
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!}
{!! Form::open(['class' => 'form-horizontal','id' => 'store','route' => 'tags.store']) !!}
<div class="row">
<div class="col-lg-5 col-md-5 col-sm-12">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-tag"></i> Mandatory fields
</div>
<div class="panel-body">
{!! ExpandedForm::text('tag') !!}
{!! ExpandedForm::multiRadio('tagMode',$tagOptions) !!}
</div>
</div>
</div>
<div class="col-lg-7 col-md-7 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
{!! ExpandedForm::date('date') !!}
{!! ExpandedForm::textarea('description') !!}
{!! ExpandedForm::location('tagPosition') !!}
</div>
</div>
<!-- panel for options -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
</div>
<div class="panel-body">
{!! ExpandedForm::optionsList('create','tag') !!}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-plus-circle"></i> Store new tag
</button>
</p>
</div>
</div>
</form>
@stop
@section('scripts')
<script type="text/javascript">
@if(Input::old('latitude'))
var latitude = "{{Input::old('latitude')}}";
@else
var latitude = "52.3167";
@endif
@if(Input::old('latitude') && Input::old('longitude') && Input::old('zoomLevel'))
var doPlaceMarker = true;
@else
var doPlaceMarker = false;
@endif
@if(Input::old('longitude'))
var longitude = "{{Input::old('longitude')}}";
@else
var longitude = "5.5500";
@endif
@if(Input::old('zoomLevel'))
var zoomLevel = {{intval(Input::old('zoomLevel'))}};
@else
var zoomLevel = 6;
@endif
</script>
<script src="https://maps.googleapis.com/maps/api/js?v=3"></script>
<script src="js/tags.js"></script>
@stop

View File

@ -0,0 +1,86 @@
@extends('layouts.default')
@section('content')
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $tag) !!}
{!! Form::model($tag, ['class' => 'form-horizontal','id' => 'update','url' => route('tags.update',$tag->id)]) !!}
<div class="row">
<div class="col-lg-5 col-md-5 col-sm-12">
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-tag"></i> Mandatory fields
</div>
<div class="panel-body">
{!! ExpandedForm::text('tag') !!}
{!! ExpandedForm::multiRadio('tagMode',$tagOptions) !!}
</div>
</div>
</div>
<div class="col-lg-7 col-md-7 col-sm-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
{!! ExpandedForm::date('date') !!}
{!! ExpandedForm::textarea('description') !!}
{!! ExpandedForm::location('tagPosition') !!}
</div>
</div>
<!-- panel for options -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
</div>
<div class="panel-body">
{!! ExpandedForm::optionsList('create','tag') !!}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-plus-circle"></i> Store new tag
</button>
</p>
</div>
</div>
</form>
@stop
@section('scripts')
<script type="text/javascript">
@if(Input::old('latitude'))
var latitude = "{{Input::old('latitude')}}";
@else
var latitude = "52.3167";
@endif
@if(Input::old('latitude') && Input::old('longitude') && Input::old('zoomLevel'))
var doPlaceMarker = true;
@else
var doPlaceMarker = false;
@endif
@if(Input::old('longitude'))
var longitude = "{{Input::old('longitude')}}";
@else
var longitude = "5.5500";
@endif
@if(Input::old('zoomLevel'))
var zoomLevel = {{intval(Input::old('zoomLevel'))}};
@else
var zoomLevel = 6;
@endif
</script>
<script src="https://maps.googleapis.com/maps/api/js?v=3"></script>
<script src="js/tags.js"></script>
@stop

View File

@ -0,0 +1,63 @@
@extends('layouts.default')
@section('content')
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading"><i class="fa fa-fw fa-tags"></i> Tags</div>
<div class="panel-body">
<div id="tagHelp" class="collapse
@if($helpHidden === false)
in
@endif
">
<p>
Usually tags are singular words, designed to quickly band items together
using things like <span class="label label-info">expensive</span>,
<span class="label label-info">bill</span> or
<span class="label label-info">for-party</span>. In Firefly III, tags can have more properties
such as a date, description and location. This allows you to join transactions together in a more meaningful
way. For example, you could make a tag called <span class="label label-success">Christmas dinner with friends</span>
and add information about the restaurant. Such tags are "singular", you would only use them for a single occasion,
perhaps with multiple transactions.
</p>
<p>
Tags group transactions together, which makes it possible to store reimbursements
(in case you front money for others) and other "balancing acts" where expenses
are summed up (the payments on your new TV) or where expenses and deposits
are cancelling each other out (buying something with saved money). It's all up to you.
Using tags the old-fashioned way is of course always possible.
</p>
<p>
Create a tag to get started or enter tags when creating new transactions.
</p>
</div>
<p>
<a data-toggle="collapse" id="tagHelpButton" href="#tagHelp" aria-expanded="false" aria-controls="tagHelp">
@if($helpHidden === false)
Hide help
@else
Show help
@endif
</a>
</p>
<p>
<a href="{{route('tags.create')}}" title="New tag" class="btn btn-info"><i class="fa fa-fw fa-tag"></i> Create new tag</a>
</p>
<p>
@if(count($tags) == 0)
<em>No tags</em>
@else
@foreach($tags as $tag)
<h4 style="display: inline;"><a class="label label-success" href="{{route('tags.show',$tag)}}">{{$tag->tag}}</a></h4>
@endforeach
@endif
</p>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
<script type="text/javascript" src="js/tags.js"></script>
@endsection

View File

@ -0,0 +1,77 @@
@extends('layouts.default')
@section('content')
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $tag) !!}
<!-- show this block only when the tag has some meta-data -->
@if($tag->latitude && $tag->longitude && $tag->zoomLevel)
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-fw {{$subTitleIcon}} fa-fw"></i> {{{$tag->tag}}}
@if($tag->date)
on {{$tag->date->format('jS F Y')}}
@endif
<!-- ACTIONS MENU -->
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li><a href="{{route('tags.edit',$tag->id)}}"><i class="fa fa-pencil fa-fw"></i> Edit</a></li>
<li><a href="{{route('tags.delete',$tag->id)}}"><i class="fa fa-trash fa-fw"></i> Delete</a></li>
</ul>
</div>
</div>
</div>
<div class="panel-body">
@if($tag->description)
<p class="text-info">
{{$tag->description}}
</p>
@endif
@if($tag->latitude && $tag->longitude && $tag->zoomLevel)
<p>
<img src="https://maps.googleapis.com/maps/api/staticmap?center={{$tag->latitude}},{{$tag->longitude}}&zoom={{$tag->zoomLevel}}&size=600x300">
</p>
@endif
</div>
</div>
</div>
</div>
@endif
<!-- if no such thing, show another block maybe? -->
<div class="row">
<div class="col-lg-612 col-md-12 col-sm-12 col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-repeat fa-fw"></i> Transactions
<!-- here is the edit menu when there is no meta-data -->
@if(!($tag->latitude && $tag->longitude && $tag->zoomLevel))
<!-- ACTIONS MENU -->
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
Actions
<span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li><a href="{{route('tags.edit',$tag->id)}}"><i class="fa fa-pencil fa-fw"></i> Edit</a></li>
<li><a href="{{route('tags.delete',$tag->id)}}"><i class="fa fa-trash fa-fw"></i> Delete</a></li>
</ul>
</div>
</div>
@endif
</div>
@include('list.journals-full',['journals' => $tag->transactionjournals])
</div>
</div>
</div>
@stop
@section('scripts')
<script type="text/javascript">
var tagID = {{{$tag->id}}};
</script>
@stop

View File

@ -53,50 +53,6 @@
</div>
</div>
@endif
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-fw fa-exchange"></i>
Related transactions
</div>
@if($journal->transactiongroups()->count() == 0)
<div class="panel-body">
<p>
<em>No related transactions</em>
</p>
</div>
@else
<table class="table">
@foreach($journal->transactiongroups()->get() as $group)
<tr>
<th colspan="2">Group #{{$group->id}} ({{$group->relation}})</th>
</tr>
@foreach($group->transactionjournals()->where('transaction_journals.id','!=',$journal->id)->get() as $jrnl)
<tr>
<td>
<a href="{{route('related.getRemoveRelation',[$journal->id, $jrnl->id])}}" class="btn btn-danger btn-xs"><span class="glyphicon glyphicon-trash"></span></a>
</td>
<td>
<a href="{{route('transactions.show',$jrnl->id)}}">{{{$jrnl->description}}}</a>
</td>
<td>
@foreach($jrnl->transactions()->get() as $t)
@if($t->amount > 0)
{!! Amount::formatTransaction($t) !!}
@endif
@endforeach
</td>
</tr>
@endforeach
</tr>
@endforeach
</table>
@endif
<div class="panel-footer">
<p>
<a href="#" data-id="{{$journal->id}}" class="relateTransaction btn btn-default"><i data-id="{{$journal->id}}" class="fa fa-compress"></i> Relate to another transaction</a>
</p>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">