mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Update, rebuild, and add a new API endpoint.
This commit is contained in:
parent
66b0d9d309
commit
44df07a5f5
12
.ci/php-cs-fixer/composer.lock
generated
12
.ci/php-cs-fixer/composer.lock
generated
@ -226,16 +226,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.46.0",
|
||||
"version": "v3.47.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2"
|
||||
"reference": "173c60d1eff911c9c54322704623a45561d3241d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2",
|
||||
"reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/173c60d1eff911c9c54322704623a45561d3241d",
|
||||
"reference": "173c60d1eff911c9c54322704623a45561d3241d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -305,7 +305,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.47.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@ -313,7 +313,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-01-03T21:38:46+00:00"
|
||||
"time": "2024-01-16T18:54:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateController.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
|
||||
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Model\Transaction\UpdateRequest;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
private TransactionGroupRepositoryInterface $groupRepository;
|
||||
|
||||
/**
|
||||
* TransactionController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/updateTransaction
|
||||
*
|
||||
* Update a transaction.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
|
||||
{
|
||||
app('log')->debug('Now in update routine for transaction group [v2]!');
|
||||
$data = $request->getAll();
|
||||
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
|
||||
$applyRules = $data['apply_rules'] ?? true;
|
||||
$fireWebhooks = $data['fire_webhooks'] ?? true;
|
||||
|
||||
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks));
|
||||
app('preferences')->mark();
|
||||
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($admin)->setTransactionGroup($transactionGroup);
|
||||
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
if (null === $selectedGroup) {
|
||||
throw new FireflyException('200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
|
||||
}
|
||||
|
||||
$transformer = new TransactionGroupTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject('transactions', $selectedGroup, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
366
app/Api/V2/Request/Model/Transaction/UpdateRequest.php
Normal file
366
app/Api/V2/Request/Model/Transaction/UpdateRequest.php
Normal file
@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/*
|
||||
* UpdateRequest.php
|
||||
* Copyright (c) 2024 james@firefly-iii.org.
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V2\Request\Model\Transaction;
|
||||
|
||||
use FireflyIII\Api\V1\Requests\Models\AvailableBudget\Request;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Rules\BelongsUser;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsDateOrTime;
|
||||
use FireflyIII\Rules\IsValidPositiveAmount;
|
||||
use FireflyIII\Rules\IsValidZeroOrMoreAmount;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\Validation\GroupValidation;
|
||||
use FireflyIII\Validation\TransactionValidation;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Class UpdateRequest
|
||||
*
|
||||
* TODO it's the same as the v1
|
||||
*/
|
||||
class UpdateRequest extends Request
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use GroupValidation;
|
||||
use TransactionValidation;
|
||||
|
||||
private array $arrayFields;
|
||||
private array $booleanFields;
|
||||
private array $dateFields;
|
||||
private array $floatFields;
|
||||
private array $integerFields;
|
||||
private array $stringFields;
|
||||
private array $textareaFields;
|
||||
|
||||
/**
|
||||
* Get all data. Is pretty complex because of all the ??-statements.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$this->integerFields = ['order', 'currency_id', 'foreign_currency_id', 'transaction_journal_id', 'source_id', 'destination_id', 'budget_id', 'category_id', 'bill_id', 'recurrence_id'];
|
||||
$this->dateFields = ['date', 'interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
|
||||
$this->textareaFields = ['notes'];
|
||||
// not really floats, for validation.
|
||||
$this->floatFields = ['amount', 'foreign_amount'];
|
||||
$this->stringFields = ['type', 'currency_code', 'foreign_currency_code', 'description', 'source_name', 'source_iban', 'source_number', 'source_bic', 'destination_name', 'destination_iban', 'destination_number', 'destination_bic', 'budget_name', 'category_name', 'bill_name', 'internal_reference', 'external_id', 'bunq_payment_id', 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', 'sepa_ci', 'sepa_batch_id', 'external_url'];
|
||||
$this->booleanFields = ['reconciled'];
|
||||
$this->arrayFields = ['tags'];
|
||||
$data = [];
|
||||
if ($this->has('transactions')) {
|
||||
$data['transactions'] = $this->getTransactionData();
|
||||
}
|
||||
if ($this->has('apply_rules')) {
|
||||
$data['apply_rules'] = $this->boolean('apply_rules', true);
|
||||
}
|
||||
if ($this->has('fire_webhooks')) {
|
||||
$data['fire_webhooks'] = $this->boolean('fire_webhooks', true);
|
||||
}
|
||||
if ($this->has('group_title')) {
|
||||
$data['group_title'] = $this->convertString('group_title');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
// basic fields for group:
|
||||
'group_title' => 'min:1|max:1000|nullable',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
|
||||
// transaction rules (in array for splits):
|
||||
'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation',
|
||||
'transactions.*.date' => [new IsDateOrTime()],
|
||||
'transactions.*.order' => 'numeric|min:0',
|
||||
|
||||
// group id:
|
||||
'transactions.*.transaction_journal_id' => ['nullable', 'numeric', new BelongsUser()],
|
||||
|
||||
// currency info
|
||||
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
|
||||
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
|
||||
'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id',
|
||||
'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code',
|
||||
|
||||
// amount
|
||||
'transactions.*.amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'transactions.*.foreign_amount' => ['nullable', new IsValidZeroOrMoreAmount()],
|
||||
|
||||
// description
|
||||
'transactions.*.description' => 'nullable|min:1|max:1000',
|
||||
|
||||
// source of transaction
|
||||
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.source_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// destination of transaction
|
||||
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()],
|
||||
'transactions.*.destination_name' => 'min:1|max:255|nullable',
|
||||
|
||||
// budget, category, bill and piggy
|
||||
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.budget_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser(), 'nullable'],
|
||||
'transactions.*.category_name' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser()],
|
||||
'transactions.*.bill_name' => ['min:1', 'max:255', 'nullable', new BelongsUser()],
|
||||
|
||||
// other interesting fields
|
||||
'transactions.*.reconciled' => [new IsBoolean()],
|
||||
'transactions.*.notes' => 'min:1|max:32768|nullable',
|
||||
'transactions.*.tags' => 'min:0|max:255|nullable',
|
||||
'transactions.*.tags.*' => 'min:0|max:255',
|
||||
|
||||
// meta info fields
|
||||
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.external_url' => sprintf('min:1|max:255|nullable|url:%s', $validProtocols),
|
||||
|
||||
// SEPA fields:
|
||||
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
|
||||
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
|
||||
|
||||
// dates
|
||||
'transactions.*.interest_date' => 'date|nullable',
|
||||
'transactions.*.book_date' => 'date|nullable',
|
||||
'transactions.*.process_date' => 'date|nullable',
|
||||
'transactions.*.due_date' => 'date|nullable',
|
||||
'transactions.*.payment_date' => 'date|nullable',
|
||||
'transactions.*.invoice_date' => 'date|nullable',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('userGroupTransaction');
|
||||
$validator->after(
|
||||
function (Validator $validator) use ($transactionGroup): void {
|
||||
// if more than one, verify that there are journal ID's present.
|
||||
$this->validateJournalIds($validator, $transactionGroup);
|
||||
|
||||
// all transaction types must be equal:
|
||||
$this->validateTransactionTypesForUpdate($validator);
|
||||
|
||||
// user wants to update a reconciled transaction.
|
||||
// source, destination, amount + foreign_amount cannot be changed
|
||||
// and must be omitted from the request.
|
||||
$this->preventUpdateReconciled($validator, $transactionGroup);
|
||||
|
||||
// validate source/destination is equal, depending on the transaction journal type.
|
||||
$this->validateEqualAccountsForUpdate($validator, $transactionGroup);
|
||||
|
||||
// see method:
|
||||
// $this->preventNoAccountInfo($validator, );
|
||||
|
||||
// validate that the currency fits the source and/or destination account.
|
||||
// validate all account info
|
||||
$this->validateAccountInformationUpdate($validator, $transactionGroup);
|
||||
}
|
||||
);
|
||||
if($validator->fails()) {
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
|
||||
if (!is_countable($transactions)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/** @var null|array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
if (!is_array($transaction)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
// default response is to update nothing in the transaction:
|
||||
$current = [];
|
||||
$current = $this->getIntegerData($current, $transaction);
|
||||
$current = $this->getStringData($current, $transaction);
|
||||
$current = $this->getNlStringData($current, $transaction);
|
||||
$current = $this->getDateData($current, $transaction);
|
||||
$current = $this->getBooleanData($current, $transaction);
|
||||
$current = $this->getArrayData($current, $transaction);
|
||||
$current = $this->getFloatData($current, $transaction);
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each field, add it to the array if a reference is present in the request:
|
||||
*
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getIntegerData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->integerFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->stringFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getNlStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->textareaFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getDateData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->dateFields as $fieldName) {
|
||||
app('log')->debug(sprintf('Now at date field %s', $fieldName));
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
app('log')->debug(sprintf('New value: "%s"', (string) $transaction[$fieldName]));
|
||||
$current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getBooleanData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->booleanFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getArrayData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->arrayFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getFloatData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->floatFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$value = $transaction[$fieldName];
|
||||
if (is_float($value)) {
|
||||
$current[$fieldName] = sprintf('%.12f', $value);
|
||||
}
|
||||
if (!is_float($value)) {
|
||||
$current[$fieldName] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
}
|
@ -76,12 +76,12 @@ class IndexController extends Controller
|
||||
}
|
||||
|
||||
// add a split for the (future) v2 release.
|
||||
$periods = [];
|
||||
$groups = [];
|
||||
$subTitle = 'TODO page subtitle in v2';
|
||||
$periods = [];
|
||||
$groups = [];
|
||||
$subTitle = 'TODO page subtitle in v2';
|
||||
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.' . $objectType);
|
||||
$types = config('firefly.transactionTypesByType.' . $objectType);
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.'.$objectType);
|
||||
$types = config('firefly.transactionTypesByType.'.$objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
@ -97,31 +97,31 @@ class IndexController extends Controller
|
||||
}
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
$startStr = $start->isoFormat($this->monthAndDayFormat);
|
||||
$endStr = $end->isoFormat($this->monthAndDayFormat);
|
||||
$subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]);
|
||||
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstJournal = $this->repository->firstNull();
|
||||
$startPeriod = null === $firstJournal ? new Carbon() : $firstJournal->date;
|
||||
$endPeriod = clone $end;
|
||||
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
|
||||
$startStr = $start->isoFormat($this->monthAndDayFormat);
|
||||
$endStr = $end->isoFormat($this->monthAndDayFormat);
|
||||
$subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]);
|
||||
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
|
||||
$firstJournal = $this->repository->firstNull();
|
||||
$startPeriod = null === $firstJournal ? new Carbon() : $firstJournal->date;
|
||||
$endPeriod = clone $end;
|
||||
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
$collector->setRange($start, $end)
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAccountInformation()
|
||||
->withAttachmentInformation();
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAccountInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
}
|
||||
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'periods', 'start', 'end'));
|
||||
}
|
||||
|
||||
@ -132,8 +132,8 @@ class IndexController extends Controller
|
||||
*/
|
||||
public function indexAll(Request $request, string $objectType)
|
||||
{
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.' . $objectType);
|
||||
$types = config('firefly.transactionTypesByType.' . $objectType);
|
||||
$subTitleIcon = config('firefly.transactionIconsByType.'.$objectType);
|
||||
$types = config('firefly.transactionTypesByType.'.$objectType);
|
||||
$page = (int)$request->get('page');
|
||||
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||
$path = route('transactions.index.all', [$objectType]);
|
||||
@ -141,20 +141,21 @@ class IndexController extends Controller
|
||||
$start = null === $first ? new Carbon() : $first->date;
|
||||
$last = $this->repository->getLast();
|
||||
$end = null !== $last ? $last->date : today(config('app.timezone'));
|
||||
$subTitle = (string)trans('firefly.all_' . $objectType);
|
||||
$subTitle = (string)trans('firefly.all_'.$objectType);
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
$collector->setRange($start, $end)
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withAccountInformation()
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAttachmentInformation();
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
->setTypes($types)
|
||||
->setLimit($pageSize)
|
||||
->setPage($page)
|
||||
->withAccountInformation()
|
||||
->withBudgetInformation()
|
||||
->withCategoryInformation()
|
||||
->withAttachmentInformation()
|
||||
;
|
||||
$groups = $collector->getPaginatedGroups();
|
||||
$groups->setPath($path);
|
||||
|
||||
return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'start', 'end'));
|
||||
|
@ -23,12 +23,12 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
/**
|
||||
* Class AppServiceProvider
|
||||
@ -52,12 +52,13 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
return response()
|
||||
->json($value)
|
||||
->withHeaders($headers);
|
||||
->withHeaders($headers)
|
||||
;
|
||||
});
|
||||
|
||||
// blade extension
|
||||
Blade::directive('activeXRoutePartial', function (string $route) {
|
||||
$name = \Route::getCurrentRoute()->getName() ?? '';
|
||||
$name = \Route::getCurrentRoute()->getName() ?? '';
|
||||
if (str_contains($name, $route)) {
|
||||
return 'menu-open';
|
||||
}
|
||||
@ -65,7 +66,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
return '';
|
||||
});
|
||||
Blade::if('partialroute', function (string $route) {
|
||||
$name = \Route::getCurrentRoute()->getName() ?? '';
|
||||
$name = \Route::getCurrentRoute()->getName() ?? '';
|
||||
if (str_contains($name, $route)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
|
||||
|
||||
public function setUser(null|Authenticatable|User $user): void
|
||||
{
|
||||
if ($user instanceof user) {
|
||||
if ($user instanceof User) {
|
||||
$this->user = $user;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,10 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BillDateCalculator
|
||||
{
|
||||
// #8401 we start keeping track of the diff in periods, because if it can't jump over a period (happens often in February)
|
||||
// we can force the process along.
|
||||
private int $diffInMonths = 0;
|
||||
|
||||
/**
|
||||
* Returns the dates a bill needs to be paid.
|
||||
*
|
||||
@ -43,12 +47,16 @@ class BillDateCalculator
|
||||
Log::debug(sprintf('Dates must be between %s and %s.', $earliest->format('Y-m-d'), $latest->format('Y-m-d')));
|
||||
Log::debug(sprintf('Bill started on %s, period is "%s", skip is %d, last paid = "%s".', $billStart->format('Y-m-d'), $period, $skip, $lastPaid?->format('Y-m-d')));
|
||||
|
||||
$daysUntilEOM = app('navigation')->daysUntilEndOfMonth($billStart);
|
||||
Log::debug(sprintf('For bill start, days until end of month is %d', $daysUntilEOM));
|
||||
|
||||
$set = new Collection();
|
||||
$currentStart = clone $earliest;
|
||||
|
||||
// 2023-06-23 subDay to fix 7655
|
||||
$currentStart->subDay();
|
||||
$loop = 0;
|
||||
|
||||
Log::debug('Start of loop');
|
||||
while ($currentStart <= $latest) {
|
||||
Log::debug(sprintf('Current start is %s', $currentStart->format('Y-m-d')));
|
||||
@ -79,8 +87,23 @@ class BillDateCalculator
|
||||
$set->push(clone $nextExpectedMatch);
|
||||
}
|
||||
|
||||
// #8401
|
||||
// a little check for when the day of the bill (ie 30th of the month) is not possible in
|
||||
// the next expected month because that month has only 28 days (i.e. february).
|
||||
// this applies to leap years as well.
|
||||
if ($daysUntilEOM < 4) {
|
||||
$nextUntilEOM = app('navigation')->daysUntilEndOfMonth($nextExpectedMatch);
|
||||
$diffEOM = $daysUntilEOM - $nextUntilEOM;
|
||||
if ($diffEOM > 0) {
|
||||
Log::debug(sprintf('Bill start is %d days from the end of the month. nextExceptedMatch is %d days from the end of the month.', $daysUntilEOM, $nextUntilEOM));
|
||||
$nextExpectedMatch->subDays(1);
|
||||
Log::debug(sprintf('Subtract %d days from next expected match, which is now %s', $diffEOM, $nextExpectedMatch->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
// 2023-10
|
||||
// for the next loop, go to end of period, THEN add day.
|
||||
Log::debug('Add one day to nextExpectedMatch/currentStart.');
|
||||
$nextExpectedMatch->addDay();
|
||||
$currentStart = clone $nextExpectedMatch;
|
||||
|
||||
@ -117,8 +140,13 @@ class BillDateCalculator
|
||||
return $billStartDate;
|
||||
}
|
||||
|
||||
$steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
|
||||
$result = clone $billStartDate;
|
||||
$steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
|
||||
if ($steps === $this->diffInMonths) {
|
||||
Log::debug(sprintf('Steps is %d, which is the same as diffInMonths (%d), so we add another 1.', $steps, $this->diffInMonths));
|
||||
++$steps;
|
||||
}
|
||||
$this->diffInMonths = $steps;
|
||||
$result = clone $billStartDate;
|
||||
if ($steps > 0) {
|
||||
--$steps;
|
||||
Log::debug(sprintf('Steps is %d, because addPeriod already adds 1.', $steps));
|
||||
|
@ -302,6 +302,13 @@ class Navigation
|
||||
return $currentEnd;
|
||||
}
|
||||
|
||||
public function daysUntilEndOfMonth(Carbon $date): int
|
||||
{
|
||||
$endOfMonth = $date->copy()->endOfMonth();
|
||||
|
||||
return $date->diffInDays($endOfMonth);
|
||||
}
|
||||
|
||||
public function diffInPeriods(string $period, int $skip, Carbon $beginning, Carbon $end): int
|
||||
{
|
||||
Log::debug(sprintf(
|
||||
|
@ -33,7 +33,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
*/
|
||||
class SetNotes implements ActionInterface
|
||||
{
|
||||
private RuleACtion $action;
|
||||
private RuleAction $action;
|
||||
|
||||
/**
|
||||
* TriggerInterface constructor.
|
||||
|
240
config/app.php
240
config/app.php
@ -21,6 +21,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use FireflyIII\Providers\AccountServiceProvider;
|
||||
use FireflyIII\Providers\AdminServiceProvider;
|
||||
use FireflyIII\Providers\AppServiceProvider;
|
||||
use FireflyIII\Providers\AttachmentServiceProvider;
|
||||
use FireflyIII\Providers\BillServiceProvider;
|
||||
use FireflyIII\Providers\BudgetServiceProvider;
|
||||
use FireflyIII\Providers\CategoryServiceProvider;
|
||||
use FireflyIII\Providers\CurrencyServiceProvider;
|
||||
use FireflyIII\Providers\EventServiceProvider;
|
||||
use FireflyIII\Providers\FireflyServiceProvider;
|
||||
use FireflyIII\Providers\JournalServiceProvider;
|
||||
use FireflyIII\Providers\PiggyBankServiceProvider;
|
||||
use FireflyIII\Providers\RecurringServiceProvider;
|
||||
use FireflyIII\Providers\RouteServiceProvider;
|
||||
use FireflyIII\Providers\RuleGroupServiceProvider;
|
||||
use FireflyIII\Providers\RuleServiceProvider;
|
||||
use FireflyIII\Providers\SearchServiceProvider;
|
||||
use FireflyIII\Providers\SessionServiceProvider;
|
||||
use FireflyIII\Providers\TagServiceProvider;
|
||||
use FireflyIII\Support\Facades\AccountForm;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\CurrencyForm;
|
||||
@ -31,6 +50,67 @@ use FireflyIII\Support\Facades\PiggyBankForm;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Facades\RuleForm;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Auth\AuthServiceProvider;
|
||||
use Illuminate\Auth\Passwords\PasswordResetServiceProvider;
|
||||
use Illuminate\Broadcasting\BroadcastServiceProvider;
|
||||
use Illuminate\Bus\BusServiceProvider;
|
||||
use Illuminate\Cache\CacheServiceProvider;
|
||||
use Illuminate\Cookie\CookieServiceProvider;
|
||||
use Illuminate\Database\DatabaseServiceProvider;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Encryption\EncryptionServiceProvider;
|
||||
use Illuminate\Filesystem\FilesystemServiceProvider;
|
||||
use Illuminate\Foundation\Providers\ConsoleSupportServiceProvider;
|
||||
use Illuminate\Foundation\Providers\FoundationServiceProvider;
|
||||
use Illuminate\Hashing\HashServiceProvider;
|
||||
use Illuminate\Mail\MailServiceProvider;
|
||||
use Illuminate\Notifications\NotificationServiceProvider;
|
||||
use Illuminate\Pagination\PaginationServiceProvider;
|
||||
use Illuminate\Pipeline\PipelineServiceProvider;
|
||||
use Illuminate\Queue\QueueServiceProvider;
|
||||
use Illuminate\Redis\RedisServiceProvider;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Translation\TranslationServiceProvider;
|
||||
use Illuminate\Validation\ValidationServiceProvider;
|
||||
use Illuminate\View\ViewServiceProvider;
|
||||
use PragmaRX\Google2FALaravel\Facade;
|
||||
use Spatie\Html\Facades\Html;
|
||||
use TwigBridge\Facade\Twig;
|
||||
use TwigBridge\ServiceProvider;
|
||||
|
||||
return [
|
||||
'name' => envNonEmpty('APP_NAME', 'Firefly III'),
|
||||
@ -44,94 +124,94 @@ return [
|
||||
'cipher' => 'AES-256-CBC',
|
||||
'providers' => [
|
||||
// Laravel Framework Service Providers...
|
||||
Illuminate\Auth\AuthServiceProvider::class,
|
||||
Illuminate\Broadcasting\BroadcastServiceProvider::class,
|
||||
Illuminate\Bus\BusServiceProvider::class,
|
||||
Illuminate\Cache\CacheServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
|
||||
Illuminate\Cookie\CookieServiceProvider::class,
|
||||
Illuminate\Database\DatabaseServiceProvider::class,
|
||||
Illuminate\Encryption\EncryptionServiceProvider::class,
|
||||
Illuminate\Filesystem\FilesystemServiceProvider::class,
|
||||
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
|
||||
Illuminate\Hashing\HashServiceProvider::class,
|
||||
Illuminate\Mail\MailServiceProvider::class,
|
||||
Illuminate\Notifications\NotificationServiceProvider::class,
|
||||
Illuminate\Pagination\PaginationServiceProvider::class,
|
||||
Illuminate\Pipeline\PipelineServiceProvider::class,
|
||||
Illuminate\Queue\QueueServiceProvider::class,
|
||||
Illuminate\Redis\RedisServiceProvider::class,
|
||||
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
|
||||
FireflyIII\Providers\SessionServiceProvider::class,
|
||||
Illuminate\Translation\TranslationServiceProvider::class,
|
||||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
AuthServiceProvider::class,
|
||||
BroadcastServiceProvider::class,
|
||||
BusServiceProvider::class,
|
||||
CacheServiceProvider::class,
|
||||
ConsoleSupportServiceProvider::class,
|
||||
CookieServiceProvider::class,
|
||||
DatabaseServiceProvider::class,
|
||||
EncryptionServiceProvider::class,
|
||||
FilesystemServiceProvider::class,
|
||||
FoundationServiceProvider::class,
|
||||
HashServiceProvider::class,
|
||||
MailServiceProvider::class,
|
||||
NotificationServiceProvider::class,
|
||||
PaginationServiceProvider::class,
|
||||
PipelineServiceProvider::class,
|
||||
QueueServiceProvider::class,
|
||||
RedisServiceProvider::class,
|
||||
PasswordResetServiceProvider::class,
|
||||
SessionServiceProvider::class,
|
||||
TranslationServiceProvider::class,
|
||||
ValidationServiceProvider::class,
|
||||
ViewServiceProvider::class,
|
||||
|
||||
// Package Service Providers...
|
||||
|
||||
// Application Service Providers...
|
||||
FireflyIII\Providers\AppServiceProvider::class,
|
||||
AppServiceProvider::class,
|
||||
FireflyIII\Providers\AuthServiceProvider::class,
|
||||
// FireflyIII\Providers\BroadcastServiceProvider::class,
|
||||
FireflyIII\Providers\EventServiceProvider::class,
|
||||
FireflyIII\Providers\RouteServiceProvider::class,
|
||||
EventServiceProvider::class,
|
||||
RouteServiceProvider::class,
|
||||
|
||||
// own stuff:
|
||||
PragmaRX\Google2FALaravel\ServiceProvider::class,
|
||||
TwigBridge\ServiceProvider::class,
|
||||
ServiceProvider::class,
|
||||
|
||||
// More service providers.
|
||||
FireflyIII\Providers\AccountServiceProvider::class,
|
||||
FireflyIII\Providers\AttachmentServiceProvider::class,
|
||||
FireflyIII\Providers\BillServiceProvider::class,
|
||||
FireflyIII\Providers\BudgetServiceProvider::class,
|
||||
FireflyIII\Providers\CategoryServiceProvider::class,
|
||||
FireflyIII\Providers\CurrencyServiceProvider::class,
|
||||
FireflyIII\Providers\FireflyServiceProvider::class,
|
||||
FireflyIII\Providers\JournalServiceProvider::class,
|
||||
FireflyIII\Providers\PiggyBankServiceProvider::class,
|
||||
FireflyIII\Providers\RuleServiceProvider::class,
|
||||
FireflyIII\Providers\RuleGroupServiceProvider::class,
|
||||
FireflyIII\Providers\SearchServiceProvider::class,
|
||||
FireflyIII\Providers\TagServiceProvider::class,
|
||||
FireflyIII\Providers\AdminServiceProvider::class,
|
||||
FireflyIII\Providers\RecurringServiceProvider::class,
|
||||
AccountServiceProvider::class,
|
||||
AttachmentServiceProvider::class,
|
||||
BillServiceProvider::class,
|
||||
BudgetServiceProvider::class,
|
||||
CategoryServiceProvider::class,
|
||||
CurrencyServiceProvider::class,
|
||||
FireflyServiceProvider::class,
|
||||
JournalServiceProvider::class,
|
||||
PiggyBankServiceProvider::class,
|
||||
RuleServiceProvider::class,
|
||||
RuleGroupServiceProvider::class,
|
||||
SearchServiceProvider::class,
|
||||
TagServiceProvider::class,
|
||||
AdminServiceProvider::class,
|
||||
RecurringServiceProvider::class,
|
||||
],
|
||||
'aliases' => [
|
||||
'App' => Illuminate\Support\Facades\App::class,
|
||||
'Artisan' => Illuminate\Support\Facades\Artisan::class,
|
||||
'Auth' => Illuminate\Support\Facades\Auth::class,
|
||||
'Blade' => Illuminate\Support\Facades\Blade::class,
|
||||
'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
|
||||
'Bus' => Illuminate\Support\Facades\Bus::class,
|
||||
'Cache' => Illuminate\Support\Facades\Cache::class,
|
||||
'Config' => Illuminate\Support\Facades\Config::class,
|
||||
'Cookie' => Illuminate\Support\Facades\Cookie::class,
|
||||
'Crypt' => Illuminate\Support\Facades\Crypt::class,
|
||||
'DB' => Illuminate\Support\Facades\DB::class,
|
||||
'Eloquent' => Illuminate\Database\Eloquent\Model::class,
|
||||
'Event' => Illuminate\Support\Facades\Event::class,
|
||||
'File' => Illuminate\Support\Facades\File::class,
|
||||
'Gate' => Illuminate\Support\Facades\Gate::class,
|
||||
'Hash' => Illuminate\Support\Facades\Hash::class,
|
||||
'Lang' => Illuminate\Support\Facades\Lang::class,
|
||||
'Log' => Illuminate\Support\Facades\Log::class,
|
||||
'Mail' => Illuminate\Support\Facades\Mail::class,
|
||||
'Notification' => Illuminate\Support\Facades\Notification::class,
|
||||
'Password' => Illuminate\Support\Facades\Password::class,
|
||||
'Queue' => Illuminate\Support\Facades\Queue::class,
|
||||
'Redirect' => Illuminate\Support\Facades\Redirect::class,
|
||||
'Redis' => Illuminate\Support\Facades\Redis::class,
|
||||
'Request' => Illuminate\Support\Facades\Request::class,
|
||||
'Response' => Illuminate\Support\Facades\Response::class,
|
||||
'Route' => Illuminate\Support\Facades\Route::class,
|
||||
'Schema' => Illuminate\Support\Facades\Schema::class,
|
||||
'Session' => Illuminate\Support\Facades\Session::class,
|
||||
'Storage' => Illuminate\Support\Facades\Storage::class,
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
'Html' => Spatie\Html\Facades\Html::class,
|
||||
'App' => App::class,
|
||||
'Artisan' => Artisan::class,
|
||||
'Auth' => Auth::class,
|
||||
'Blade' => Blade::class,
|
||||
'Broadcast' => Broadcast::class,
|
||||
'Bus' => Bus::class,
|
||||
'Cache' => Cache::class,
|
||||
'Config' => Config::class,
|
||||
'Cookie' => Cookie::class,
|
||||
'Crypt' => Crypt::class,
|
||||
'DB' => DB::class,
|
||||
'Eloquent' => Model::class,
|
||||
'Event' => Event::class,
|
||||
'File' => File::class,
|
||||
'Gate' => Gate::class,
|
||||
'Hash' => Hash::class,
|
||||
'Lang' => Lang::class,
|
||||
'Log' => Log::class,
|
||||
'Mail' => Mail::class,
|
||||
'Notification' => Notification::class,
|
||||
'Password' => Password::class,
|
||||
'Queue' => Queue::class,
|
||||
'Redirect' => Redirect::class,
|
||||
'Redis' => Redis::class,
|
||||
'Request' => Request::class,
|
||||
'Response' => Response::class,
|
||||
'Route' => Route::class,
|
||||
'Schema' => Schema::class,
|
||||
'Session' => Session::class,
|
||||
'Storage' => Storage::class,
|
||||
'URL' => URL::class,
|
||||
'Validator' => Validator::class,
|
||||
'View' => View::class,
|
||||
'Html' => Html::class,
|
||||
'Preferences' => Preferences::class,
|
||||
'FireflyConfig' => FireflyConfig::class,
|
||||
'Navigation' => Navigation::class,
|
||||
@ -142,12 +222,12 @@ return [
|
||||
'AccountForm' => AccountForm::class,
|
||||
'PiggyBankForm' => PiggyBankForm::class,
|
||||
'RuleForm' => RuleForm::class,
|
||||
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
|
||||
'Twig' => TwigBridge\Facade\Twig::class,
|
||||
'Google2FA' => Facade::class,
|
||||
'Twig' => Twig::class,
|
||||
|
||||
'Arr' => Illuminate\Support\Arr::class,
|
||||
'Http' => Illuminate\Support\Facades\Http::class,
|
||||
'Str' => Illuminate\Support\Str::class,
|
||||
'Arr' => Arr::class,
|
||||
'Http' => Http::class,
|
||||
'Str' => Str::class,
|
||||
],
|
||||
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
use FireflyIII\User;
|
||||
|
||||
if ('ldap' === strtolower((string)env('AUTHENTICATION_GUARD'))) {
|
||||
exit('LDAP is no longer supported by Firefly III v5.7+. Sorry about that. You will have to switch to "remote_user_guard", and use tools like Authelia or Keycloak to use LDAP together with Firefly III.');
|
||||
@ -96,11 +97,11 @@ return [
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => FireflyIII\User::class,
|
||||
'model' => User::class,
|
||||
],
|
||||
'remote_user_provider' => [
|
||||
'driver' => 'remote_user_provider',
|
||||
'model' => FireflyIII\User::class,
|
||||
'model' => User::class,
|
||||
],
|
||||
],
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
use Diglactic\Breadcrumbs\Generator;
|
||||
use Diglactic\Breadcrumbs\Manager;
|
||||
|
||||
return [
|
||||
/*
|
||||
@ -90,8 +92,8 @@ return [
|
||||
*/
|
||||
|
||||
// Manager
|
||||
'manager-class' => Diglactic\Breadcrumbs\Manager::class,
|
||||
'manager-class' => Manager::class,
|
||||
|
||||
// Generator
|
||||
'generator-class' => Diglactic\Breadcrumbs\Generator::class,
|
||||
'generator-class' => Generator::class,
|
||||
];
|
||||
|
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
use FireflyIII\User;
|
||||
|
||||
return [
|
||||
/*
|
||||
@ -51,7 +52,7 @@ return [
|
||||
],
|
||||
|
||||
'stripe' => [
|
||||
'model' => FireflyIII\User::class,
|
||||
'model' => User::class,
|
||||
'key' => env('STRIPE_KEY'),
|
||||
'secret' => env('STRIPE_SECRET'),
|
||||
],
|
||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -14,7 +14,6 @@
|
||||
"chart.js": "^4.4.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"dark-editable": "github:DarKsandr/dark-editable",
|
||||
"date-fns": "^3.2.0",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
@ -32,9 +31,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.23.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz",
|
||||
"integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==",
|
||||
"version": "7.23.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz",
|
||||
"integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -592,9 +591,6 @@
|
||||
"node-fetch": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/dark-editable": {
|
||||
"resolved": "git+ssh://git@github.com/DarKsandr/dark-editable.git#8601f10ef5a49ff9ad6f1c0921d523f17f174036"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.2.0.tgz",
|
||||
@ -663,9 +659,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.4",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
||||
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
@ -22,7 +22,6 @@
|
||||
"chart.js": "^4.4.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"dark-editable": "github:DarKsandr/dark-editable",
|
||||
"date-fns": "^3.2.0",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next-chained-backend": "^4.6.2",
|
||||
|
File diff suppressed because one or more lines are too long
1
public/build/assets/create-dd6750e8.js
Normal file
1
public/build/assets/create-dd6750e8.js
Normal file
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{f as n}from"./vendor-fca45885.js";function e(){return{id:"",name:"",alpine_name:""}}function o(){return{description:[],amount:[],currency_code:[],foreign_amount:[],foreign_currency_code:[],source_account:[],destination_account:[],budget_id:[],category_name:[],piggy_bank_id:[],bill_id:[],tags:[],notes:[],internal_reference:[],external_url:[],latitude:[],longitude:[],zoom_level:[],date:[],interest_date:[],book_date:[],process_date:[],due_date:[],payment_date:[],invoice_date:[]}}function d(){let t=n(new Date,"yyyy-MM-dd HH:mm");return{description:"",amount:"",currency_code:"EUR",foreign_amount:"",foreign_currency_code:"",source_account:e(),destination_account:e(),budget_id:null,category_name:"",piggy_bank_id:null,bill_id:null,tags:[],notes:"",internal_reference:"",external_url:"",hasLocation:!1,latitude:null,longitude:null,zoomLevel:null,date:t,interest_date:"",book_date:"",process_date:"",due_date:"",payment_date:"",invoice_date:"",errors:o()}}export{d as c,o as d};
|
||||
import{f as n}from"./vendor-0ba1deae.js";function e(){return{id:"",name:"",alpine_name:""}}function o(){return{description:[],amount:[],currency_code:[],foreign_amount:[],foreign_currency_code:[],source_account:[],destination_account:[],budget_id:[],category_name:[],piggy_bank_id:[],bill_id:[],tags:[],notes:[],internal_reference:[],external_url:[],latitude:[],longitude:[],zoom_level:[],date:[],interest_date:[],book_date:[],process_date:[],due_date:[],payment_date:[],invoice_date:[]}}function d(){let t=n(new Date,"yyyy-MM-dd HH:mm");return{description:"",amount:"",currency_code:"EUR",foreign_amount:"",foreign_currency_code:"",source_account:e(),destination_account:e(),budget_id:null,category_name:"",piggy_bank_id:null,bill_id:null,tags:[],notes:"",internal_reference:"",external_url:"",hasLocation:!1,latitude:null,longitude:null,zoomLevel:null,date:t,interest_date:"",book_date:"",process_date:"",due_date:"",payment_date:"",invoice_date:"",errors:o()}}export{d as c,o as d};
|
File diff suppressed because one or more lines are too long
1
public/build/assets/edit-74c97acb.js
Normal file
1
public/build/assets/edit-74c97acb.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
import{a as s}from"./format-money-43e05102.js";class p{list(a){return s.get("/api/v2/transactions",{params:a})}show(a,t){return s.get("/api/v2/transactions/"+a,{params:t})}}export{p as G};
|
||||
import{a as s}from"./format-money-a71f277a.js";class p{list(a){return s.get("/api/v2/transactions",{params:a})}show(a,t){return s.get("/api/v2/transactions/"+a,{params:t})}}export{p as G};
|
@ -1 +1 @@
|
||||
import{a as s}from"./format-money-43e05102.js";let t=class{list(a){return s.get("/api/v2/subscriptions",{params:a})}paid(a){return s.get("/api/v2/subscriptions/sum/paid",{params:a})}unpaid(a){return s.get("/api/v2/subscriptions/sum/unpaid",{params:a})}};class e{list(a){return s.get("/api/v2/piggy-banks",{params:a})}}export{t as G,e as a};
|
||||
import{a as s}from"./format-money-a71f277a.js";let t=class{list(a){return s.get("/api/v2/subscriptions",{params:a})}paid(a){return s.get("/api/v2/subscriptions/sum/paid",{params:a})}unpaid(a){return s.get("/api/v2/subscriptions/sum/unpaid",{params:a})}};class e{list(a){return s.get("/api/v2/piggy-banks",{params:a})}}export{t as G,e as a};
|
@ -1 +0,0 @@
|
||||
import{d as c,f as d}from"./format-money-43e05102.js";import{f,i as r}from"./vendor-fca45885.js";import{G as p}from"./get-a29f6df7.js";let g=function(){return{notifications:{error:{show:!1,text:"",url:""},success:{show:!1,text:"",url:""},wait:{show:!1,text:""}},transactions:[],totalPages:1,perPage:50,page:1,tableColumns:{description:{enabled:!0},source:{enabled:!0},destination:{enabled:!0},amount:{enabled:!0}},formatMoney(a,i){return d(a,i)},format(a){return f(a,r.t("config.date_time_fns"))},init(){this.notifications.wait.show=!0,this.notifications.wait.text=r.t("firefly.wait_loading_data"),this.getTransactions(this.page)},getTransactions(a){new p().list({page:a}).then(t=>{this.parseTransactions(t.data.data),this.totalPages=t.data.meta.pagination.total_pages,this.perPage=t.data.meta.pagination.per_page,this.page=t.data.meta.pagination.current_page}).catch(t=>{this.notifications.wait.show=!1,this.notifications.error.show=!0,this.notifications.error.text=t.response.data.message})},parseTransactions(a){for(let i in a)if(a.hasOwnProperty(i)){let t=a[i],u=t.attributes.transactions.length>1,s=!0;for(let o in t.attributes.transactions)if(t.attributes.transactions.hasOwnProperty(o)){let e=t.attributes.transactions[o];e.split=u,e.firstSplit=s,e.group_title=t.attributes.group_title,e.id=t.id,e.created_at=t.attributes.created_at,e.updated_at=t.attributes.updated_at,e.user=t.attributes.user,e.user_group=t.attributes.user_group,s=!1,console.log(e),this.transactions.push(e)}}this.notifications.wait.show=!1}}},n={index:g,dates:c};function l(){Object.keys(n).forEach(a=>{console.log(`Loading page component "${a}"`);let i=n[a]();Alpine.data(a,()=>i)}),Alpine.start()}document.addEventListener("firefly-iii-bootstrapped",()=>{console.log("Loaded through event listener."),l()});window.bootstrapped&&(console.log("Loaded through window variable."),l());
|
1
public/build/assets/index-cf70f728.js
Normal file
1
public/build/assets/index-cf70f728.js
Normal file
@ -0,0 +1 @@
|
||||
import{d as c,f as d}from"./format-money-a71f277a.js";import{f as p,i as n}from"./vendor-0ba1deae.js";import{G as f}from"./get-971543fd.js";let g=function(){return{notifications:{error:{show:!1,text:"",url:""},success:{show:!1,text:"",url:""},wait:{show:!1,text:""}},transactions:[],totalPages:1,perPage:50,page:1,tableColumns:{description:{enabled:!0},source:{enabled:!0},destination:{enabled:!0},amount:{enabled:!0}},formatMoney(t,i){return d(t,i)},format(t){return p(t,n.t("config.date_time_fns"))},init(){this.notifications.wait.show=!0,this.notifications.wait.text=n.t("firefly.wait_loading_data"),this.getTransactions(this.page)},getTransactions(t){const i=window.location.href.split("/"),a=i[i.length-1];new f().list({page:t,type:a}).then(s=>{this.parseTransactions(s.data.data),this.totalPages=s.data.meta.pagination.total_pages,this.perPage=s.data.meta.pagination.per_page,this.page=s.data.meta.pagination.current_page}).catch(s=>{this.notifications.wait.show=!1,this.notifications.error.show=!0,this.notifications.error.text=s.response.data.message})},parseTransactions(t){for(let i in t)if(t.hasOwnProperty(i)){let a=t[i],o=a.attributes.transactions.length>1,s=!0;for(let r in a.attributes.transactions)if(a.attributes.transactions.hasOwnProperty(r)){let e=a.attributes.transactions[r];e.split=o,e.firstSplit=s,e.group_title=a.attributes.group_title,e.id=a.id,e.created_at=a.attributes.created_at,e.updated_at=a.attributes.updated_at,e.user=a.attributes.user,e.user_group=a.attributes.user_group,s=!1,console.log(e),this.transactions.push(e)}}this.notifications.wait.show=!1}}},l={index:g,dates:c};function u(){Object.keys(l).forEach(t=>{console.log(`Loading page component "${t}"`);let i=l[t]();Alpine.data(t,()=>i)}),Alpine.start()}document.addEventListener("firefly-iii-bootstrapped",()=>{console.log("Loaded through event listener."),u()});window.bootstrapped&&(console.log("Loaded through window variable."),u());
|
@ -1 +1 @@
|
||||
import{c as r}from"./create-empty-split-fb5bda92.js";import{f as o}from"./vendor-fca45885.js";function c(a){let n=[];for(let i in a)if(a.hasOwnProperty(i)){let e=a[i],t=r();t.bill_id=e.bill_id,t.bill_name=e.bill_name,t.budget_id=e.budget_id,t.budget_name=e.budget_name,t.category_name=e.category_name,t.category_id=e.category_id,t.piggy_bank_id=e.piggy_bank_id,t.piggy_bank_name=e.piggy_bank_name,t.book_date=e.book_date,t.due_date=e.due_date,t.interest_date=e.interest_date,t.invoice_date=e.invoice_date,t.payment_date=e.payment_date,t.process_date=e.process_date,t.external_url=e.external_url,t.internal_reference=e.internal_reference,t.notes=e.notes,t.tags=e.tags,t.amount=parseFloat(e.amount).toFixed(e.currency_decimal_places),t.currency_code=e.currency_code,e.foreign_amount!==null&&(t.forein_currency_code=e.foreign_currency_code,t.foreign_amount=parseFloat(e.foreign_amount).toFixed(e.foreign_currency_decimal_places)),t.date=o(new Date(e.date),"yyyy-MM-dd HH:mm"),t.description=e.description,t.destination_account={id:e.destination_id,name:e.destination_name,type:e.destination_type,alpine_name:e.destination_name},t.source_account={id:e.source_id,name:e.source_name,type:e.source_type,alpine_name:e.source_name},e.latitude!==null&&(t.hasLocation=!0,t.latitude=e.latitude,t.longitude=e.longitude,t.zoomLevel=e.zoom_level),n.push(t)}return n}export{c as p};
|
||||
import{c as r}from"./create-empty-split-b2c15454.js";import{f as o}from"./vendor-0ba1deae.js";function c(a){let n=[];for(let i in a)if(a.hasOwnProperty(i)){let e=a[i],t=r();t.bill_id=e.bill_id,t.bill_name=e.bill_name,t.budget_id=e.budget_id,t.budget_name=e.budget_name,t.category_name=e.category_name,t.category_id=e.category_id,t.piggy_bank_id=e.piggy_bank_id,t.piggy_bank_name=e.piggy_bank_name,t.book_date=e.book_date,t.due_date=e.due_date,t.interest_date=e.interest_date,t.invoice_date=e.invoice_date,t.payment_date=e.payment_date,t.process_date=e.process_date,t.external_url=e.external_url,t.internal_reference=e.internal_reference,t.notes=e.notes,t.tags=e.tags,t.amount=parseFloat(e.amount).toFixed(e.currency_decimal_places),t.currency_code=e.currency_code,e.foreign_amount!==null&&(t.forein_currency_code=e.foreign_currency_code,t.foreign_amount=parseFloat(e.foreign_amount).toFixed(e.foreign_currency_decimal_places)),t.date=o(new Date(e.date),"yyyy-MM-dd HH:mm"),t.description=e.description,t.destination_account={id:e.destination_id,name:e.destination_name,type:e.destination_type,alpine_name:e.destination_name},t.source_account={id:e.source_id,name:e.source_name,type:e.source_type,alpine_name:e.source_name},e.latitude!==null&&(t.hasLocation=!0,t.latitude=e.latitude,t.longitude=e.longitude,t.zoomLevel=e.zoom_level),n.push(t)}return n}export{c as p};
|
1
public/build/assets/show-19f421da.js
Normal file
1
public/build/assets/show-19f421da.js
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
import{d,f as p}from"./format-money-43e05102.js";import{f as c,i as o}from"./vendor-fca45885.js";import{G as l}from"./get-a29f6df7.js";import{p as f}from"./parse-downloaded-splits-4afea167.js";import"./create-empty-split-fb5bda92.js";let h=function(){return{notifications:{error:{show:!1,text:"",url:""},success:{show:!1,text:"",url:""},wait:{show:!1,text:""}},groupProperties:{id:0,transactionType:"",transactionTypeTranslated:"",title:"",date:new Date},dateFields:["book_date","due_date","interest_date","invoice_date","payment_date","process_date"],metaFields:["external_id","internal_reference","sepa_batch_id","sepa_ct_id","sepa_ct_op","sepa_db","sepa_country","sepa_cc","sepa_ep","sepa_ci","external_url"],amounts:{},entries:[],pageProperties:{},formatMoney(e,s){return p(e,s)},format(e){return c(e,o.t("config.date_time_fns"))},init(){this.notifications.wait.show=!0,this.notifications.wait.text=o.t("firefly.wait_loading_data");const e=window.location.href.split("/"),s=parseInt(e[e.length-1]);new l().show(s,{}).then(t=>{const i=t.data.data;this.groupProperties.id=parseInt(i.id),this.groupProperties.transactionType=i.attributes.transactions[0].type,this.groupProperties.transactionTypeTranslated=o.t("firefly."+i.attributes.transactions[0].type),this.groupProperties.title=i.attributes.title??i.attributes.transactions[0].description,this.entries=f(i.attributes.transactions),this.notifications.wait.show=!1}).then(()=>{for(let t in this.entries)if(this.entries.hasOwnProperty(t)){const i=this.entries[t].currency_code,a=this.entries[t].foreign_currency_code;this.amounts[i]===void 0&&(this.amounts[i]=0,this.amounts[i]+=parseFloat(this.entries[t].amount)),a!==null&&a!==""&&this.amounts[a]===void 0&&(this.amounts[a]=0,this.amounts[a]+=parseFloat(this.entries[t].foreign_amount)),parseInt(t)===0&&(this.groupProperties.date=this.entries[t].date)}}).catch(t=>{this.notifications.error.show=!0,this.notifications.error.text=t.message})}}},r={show:h,dates:d};function n(){Object.keys(r).forEach(e=>{console.log(`Loading page component "${e}"`);let s=r[e]();Alpine.data(e,()=>s)}),Alpine.start()}document.addEventListener("firefly-iii-bootstrapped",()=>{console.log("Loaded through event listener."),n()});window.bootstrapped&&(console.log("Loaded through window variable."),n());
|
1
public/build/assets/show-8b1429e5.css
Normal file
1
public/build/assets/show-8b1429e5.css
Normal file
@ -0,0 +1 @@
|
||||
.dark-editable-element{border-bottom:dashed 1px #0088cc;text-decoration:none;cursor:pointer}.dark-editable-element-disabled{border-bottom:none;cursor:default}.dark-editable-element-empty{font-style:italic;color:#d14}.dark-editable{max-width:none}.dark-editable-loader{font-size:5px;left:50%;top:50%;width:1em;height:1em;border-radius:50%;position:relative;text-indent:-9999em;-webkit-animation:load5 1.1s infinite ease;animation:load5 1.1s infinite ease;-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}@-webkit-keyframes load5{0%,to{box-shadow:0 -2.6em #000,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #00000080,-1.8em -1.8em #000000b3}12.5%{box-shadow:0 -2.6em #000000b3,1.8em -1.8em #000,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #00000080}25%{box-shadow:0 -2.6em #00000080,1.8em -1.8em #000000b3,2.5em 0 #000,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}37.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #00000080,2.5em 0 #000000b3,1.75em 1.75em #000,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}50%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #00000080,1.75em 1.75em #000000b3,0 2.5em #000,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}62.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #00000080,0 2.5em #000000b3,-1.8em 1.8em #000,-2.6em 0 #0003,-1.8em -1.8em #0003}75%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #00000080,-1.8em 1.8em #000000b3,-2.6em 0 #000,-1.8em -1.8em #0003}87.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #00000080,-2.6em 0 #000000b3,-1.8em -1.8em #000}}@keyframes load5{0%,to{box-shadow:0 -2.6em #000,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #00000080,-1.8em -1.8em #000000b3}12.5%{box-shadow:0 -2.6em #000000b3,1.8em -1.8em #000,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #00000080}25%{box-shadow:0 -2.6em #00000080,1.8em -1.8em #000000b3,2.5em 0 #000,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}37.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #00000080,2.5em 0 #000000b3,1.75em 1.75em #000,0 2.5em #0003,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}50%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #00000080,1.75em 1.75em #000000b3,0 2.5em #000,-1.8em 1.8em #0003,-2.6em 0 #0003,-1.8em -1.8em #0003}62.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #00000080,0 2.5em #000000b3,-1.8em 1.8em #000,-2.6em 0 #0003,-1.8em -1.8em #0003}75%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #00000080,-1.8em 1.8em #000000b3,-2.6em 0 #000,-1.8em -1.8em #0003}87.5%{box-shadow:0 -2.6em #0003,1.8em -1.8em #0003,2.5em 0 #0003,1.75em 1.75em #0003,0 2.5em #0003,-1.8em 1.8em #00000080,-2.6em 0 #000000b3,-1.8em -1.8em #000}}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,50 +1,50 @@
|
||||
{
|
||||
"_create-empty-split-fb5bda92.js": {
|
||||
"file": "assets/create-empty-split-fb5bda92.js",
|
||||
"_create-empty-split-b2c15454.js": {
|
||||
"file": "assets/create-empty-split-b2c15454.js",
|
||||
"imports": [
|
||||
"_vendor-fca45885.js"
|
||||
"_vendor-0ba1deae.js"
|
||||
],
|
||||
"integrity": "sha384-hhKUcZf3xSLJgwkzufZGpLjOUeD5gulL71KQu72SoGIUVLPU3EIBT+ICT/znc01b"
|
||||
"integrity": "sha384-LPTZ0hEPbyGHGP76xXmeyTW22lbs5CNJU8wwXx3SOeUf6jDNYVoSSEe05QdKYKQ0"
|
||||
},
|
||||
"_format-money-43e05102.js": {
|
||||
"file": "assets/format-money-43e05102.js",
|
||||
"_format-money-a71f277a.js": {
|
||||
"file": "assets/format-money-a71f277a.js",
|
||||
"imports": [
|
||||
"_vendor-fca45885.js"
|
||||
"_vendor-0ba1deae.js"
|
||||
],
|
||||
"integrity": "sha384-/lP2o0Yj4DAtc93ChQvqF5ctDGjH6jakuIZcEQBICvDn/YtSDaPr7PzrZd4w53jV"
|
||||
"integrity": "sha384-NX1AQwixTtbRYpnSgnTwE6OzMgA0zTe/BOQp4tA+sMFZ7A/Z/8AM85Ia0343u020"
|
||||
},
|
||||
"_get-94678769.js": {
|
||||
"file": "assets/get-94678769.js",
|
||||
"_get-971543fd.js": {
|
||||
"file": "assets/get-971543fd.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js"
|
||||
"_format-money-a71f277a.js"
|
||||
],
|
||||
"integrity": "sha384-/nzI9V1buTN8n1p1uL9UOxOasx6+q6kTpiwDCaZZHBCVrrnFqWSCQCHAPQFcqYGB"
|
||||
"integrity": "sha384-kRyC3Ngpm92ejZXmYiYd9NWyzp47VMYlXB1QCIMNJf/5g0CM3/id1tMx1wM7sWop"
|
||||
},
|
||||
"_get-a29f6df7.js": {
|
||||
"file": "assets/get-a29f6df7.js",
|
||||
"_get-c4c00bed.js": {
|
||||
"file": "assets/get-c4c00bed.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js"
|
||||
"_format-money-a71f277a.js"
|
||||
],
|
||||
"integrity": "sha384-QRZtv6rXwaf1k9JOU1p1T42Nn2t5En83j/8rbh00dJ+1sSoNLD0maS8FkPKVRbpE"
|
||||
"integrity": "sha384-KLoBbucwHe+WIBcGunE9+tql5dx39uDkibZZDSq9X9m25oHNXRtVbjfI8ljmGSYe"
|
||||
},
|
||||
"_parse-downloaded-splits-4afea167.js": {
|
||||
"file": "assets/parse-downloaded-splits-4afea167.js",
|
||||
"_parse-downloaded-splits-19845c1d.js": {
|
||||
"file": "assets/parse-downloaded-splits-19845c1d.js",
|
||||
"imports": [
|
||||
"_create-empty-split-fb5bda92.js",
|
||||
"_vendor-fca45885.js"
|
||||
"_create-empty-split-b2c15454.js",
|
||||
"_vendor-0ba1deae.js"
|
||||
],
|
||||
"integrity": "sha384-rTgD6HzwbdSD/qoN3b4vipowqanriBYLYnORI3F23R+uFbr1kyZIE1AHYA9oe2qO"
|
||||
"integrity": "sha384-fvvNIwjZ9BkUU+LRImRK606sDo4vJs8/Z58Vm7T46vAl66xckgxX+GVjsltwkhus"
|
||||
},
|
||||
"_splice-errors-into-transactions-9c9b99c0.js": {
|
||||
"file": "assets/splice-errors-into-transactions-9c9b99c0.js",
|
||||
"_splice-errors-into-transactions-995deb6d.js": {
|
||||
"file": "assets/splice-errors-into-transactions-995deb6d.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_get-94678769.js",
|
||||
"_vendor-fca45885.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_get-c4c00bed.js",
|
||||
"_vendor-0ba1deae.js"
|
||||
],
|
||||
"integrity": "sha384-6QHZSbNE9w8hPmdvtcEi1wqimo8+lyJWXBaYaX7/c8qoveRKX5A2WZVhqb7bMRkV"
|
||||
"integrity": "sha384-xy9kpKxM6h0137K3N9ZyXSqNwsD3sk+KrMm3tWivk/WtgXy+fxjgk08YSX7Zgc+d"
|
||||
},
|
||||
"_vendor-fca45885.js": {
|
||||
"_vendor-0ba1deae.js": {
|
||||
"assets": [
|
||||
"assets/layers-1dbbe9d0.png",
|
||||
"assets/layers-2x-066daca8.png",
|
||||
@ -53,8 +53,8 @@
|
||||
"css": [
|
||||
"assets/vendor-49001d3f.css"
|
||||
],
|
||||
"file": "assets/vendor-fca45885.js",
|
||||
"integrity": "sha384-xc7wHYzykc3pL46xJ1C3dJ/yReuYh0aAcdywRaE8sPW9QiKDkmpdip8ksd8qWp4b"
|
||||
"file": "assets/vendor-0ba1deae.js",
|
||||
"integrity": "sha384-9W7xmHUksAccX1L6MeAPa77/siJb7Gka71e+TA2dfgu4VV7xUgeqeyTCIShNLB4b"
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf": {
|
||||
"file": "assets/fa-brands-400-5656d596.ttf",
|
||||
@ -102,68 +102,76 @@
|
||||
"integrity": "sha384-wg83fCOXjBtqzFAWhTL9Sd9vmLUNhfEEzfmNUX9zwv2igKlz/YQbdapF4ObdxF+R"
|
||||
},
|
||||
"resources/assets/v2/pages/dashboard/dashboard.js": {
|
||||
"file": "assets/dashboard-2ad8d866.js",
|
||||
"file": "assets/dashboard-60d58953.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_vendor-fca45885.js",
|
||||
"_get-a29f6df7.js",
|
||||
"_get-94678769.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_vendor-0ba1deae.js",
|
||||
"_get-971543fd.js",
|
||||
"_get-c4c00bed.js"
|
||||
],
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/pages/dashboard/dashboard.js",
|
||||
"integrity": "sha384-drEV1+zgjVGIfpdo1BovDls72a22l303XFZq/n0BE9VMTuydOYDBBgEqjM1GgwQD"
|
||||
"integrity": "sha384-KGiwIVYJZwLUgiraYW27lW+R5wW0LW30/Z0z6UA1SkfjjoeDXm1DTcSoUtMhltaG"
|
||||
},
|
||||
"resources/assets/v2/pages/transactions/create.js": {
|
||||
"file": "assets/create-46b63de1.js",
|
||||
"file": "assets/create-dd6750e8.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_create-empty-split-fb5bda92.js",
|
||||
"_splice-errors-into-transactions-9c9b99c0.js",
|
||||
"_vendor-fca45885.js",
|
||||
"_get-94678769.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_create-empty-split-b2c15454.js",
|
||||
"_splice-errors-into-transactions-995deb6d.js",
|
||||
"_vendor-0ba1deae.js",
|
||||
"_get-c4c00bed.js"
|
||||
],
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/pages/transactions/create.js",
|
||||
"integrity": "sha384-qacGHBXX2jXhiYdstJdUk5SBbdF1y8uj9RTdWetpmATLMNSjetRemPMk4PJoGxgZ"
|
||||
"integrity": "sha384-rrUkO07lnTiSQ+5SPMwOLmnu2J+5oQmZFKfsf3ApkQG9ct3qrwBocQ9f0H5cftv2"
|
||||
},
|
||||
"resources/assets/v2/pages/transactions/edit.js": {
|
||||
"file": "assets/edit-b45ed381.js",
|
||||
"file": "assets/edit-74c97acb.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_get-a29f6df7.js",
|
||||
"_parse-downloaded-splits-4afea167.js",
|
||||
"_splice-errors-into-transactions-9c9b99c0.js",
|
||||
"_vendor-fca45885.js",
|
||||
"_create-empty-split-fb5bda92.js",
|
||||
"_get-94678769.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_get-971543fd.js",
|
||||
"_parse-downloaded-splits-19845c1d.js",
|
||||
"_splice-errors-into-transactions-995deb6d.js",
|
||||
"_vendor-0ba1deae.js",
|
||||
"_create-empty-split-b2c15454.js",
|
||||
"_get-c4c00bed.js"
|
||||
],
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/pages/transactions/edit.js",
|
||||
"integrity": "sha384-CoTAYJsk4/69IMBbgV50+i5mK+pANKXl0hLH9nqErgit1WDdBK6U4VimDsq5fvMf"
|
||||
"integrity": "sha384-gU2Nw6D5i8slOZwzR1Tlpf82FvESkn7cNoT24Pk/VJzqY3JQo3Mwr0vb09swPwEZ"
|
||||
},
|
||||
"resources/assets/v2/pages/transactions/index.js": {
|
||||
"file": "assets/index-8df22335.js",
|
||||
"file": "assets/index-cf70f728.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_vendor-fca45885.js",
|
||||
"_get-a29f6df7.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_vendor-0ba1deae.js",
|
||||
"_get-971543fd.js"
|
||||
],
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/pages/transactions/index.js",
|
||||
"integrity": "sha384-UcghdFUsV8Bp01abyYn4u6sI/mWjXuY677vyQ76p1gVaMl9pVMGjdPa8Cd9Lg6M5"
|
||||
"integrity": "sha384-aCoGtjOpn/zM6UGPc4bOZEGISPRJd8Vd2l/7VbFnJzjrSzMRIv5hhU1vpvw0JoTh"
|
||||
},
|
||||
"resources/assets/v2/pages/transactions/show.css": {
|
||||
"file": "assets/show-8b1429e5.css",
|
||||
"src": "resources/assets/v2/pages/transactions/show.css",
|
||||
"integrity": "sha384-D0+ADFJ1uVTJKTOYOVIjDXI7vzboM0WuC0AjpA2jhwEXxAVGnZTzqFZPuSYWBWiL"
|
||||
},
|
||||
"resources/assets/v2/pages/transactions/show.js": {
|
||||
"file": "assets/show-61e79d17.js",
|
||||
"css": [
|
||||
"assets/show-8b1429e5.css"
|
||||
],
|
||||
"file": "assets/show-19f421da.js",
|
||||
"imports": [
|
||||
"_format-money-43e05102.js",
|
||||
"_vendor-fca45885.js",
|
||||
"_get-a29f6df7.js",
|
||||
"_parse-downloaded-splits-4afea167.js",
|
||||
"_create-empty-split-fb5bda92.js"
|
||||
"_format-money-a71f277a.js",
|
||||
"_vendor-0ba1deae.js",
|
||||
"_get-971543fd.js",
|
||||
"_parse-downloaded-splits-19845c1d.js",
|
||||
"_create-empty-split-b2c15454.js"
|
||||
],
|
||||
"isEntry": true,
|
||||
"src": "resources/assets/v2/pages/transactions/show.js",
|
||||
"integrity": "sha384-JBLT+bepNQ1x9TTUWy1H/ekjto18Jzudb4suKq+rF1YvqeSpouHzJU2l1YeObQ7y"
|
||||
"integrity": "sha384-Ml/m6l4ZRqzuhW7/z2hKnrLYZKKkrswMA60W6eWSMeQkNXgzcgO4lO9YS8iYHO2O"
|
||||
},
|
||||
"resources/assets/v2/sass/app.scss": {
|
||||
"file": "assets/app-fb7b26ec.css",
|
||||
|
42
resources/assets/v2/libraries/dark-editable/Modes/BaseMode.js
Executable file
42
resources/assets/v2/libraries/dark-editable/Modes/BaseMode.js
Executable file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* BaseMode.js
|
||||
* Copyright (c) 2024 https://github.com/DarKsandr/dark-editable
|
||||
*
|
||||
* License: MIT
|
||||
*
|
||||
* Copied and slightly edited by James Cole <james@firefly-iii.org>
|
||||
*/
|
||||
export default class BaseMode{
|
||||
constructor(context) {
|
||||
if(this.constructor === BaseMode){
|
||||
throw new Error(`It's abstract class`);
|
||||
}
|
||||
this.context = context;
|
||||
}
|
||||
event_show(){
|
||||
this.context.typeElement.hideError();
|
||||
this.context.typeElement.element.value = this.context.value;
|
||||
this.context.element.dispatchEvent(new CustomEvent("show"));
|
||||
}
|
||||
event_shown(){
|
||||
this.context.element.dispatchEvent(new CustomEvent("shown"));
|
||||
}
|
||||
event_hide(){
|
||||
this.context.element.dispatchEvent(new CustomEvent("hide"));
|
||||
}
|
||||
event_hidden(){
|
||||
this.context.element.dispatchEvent(new CustomEvent("hidden"));
|
||||
}
|
||||
init(){
|
||||
throw new Error('Method `init` not define!');
|
||||
}
|
||||
enable(){
|
||||
throw new Error('Method `enable` not define!');
|
||||
}
|
||||
disable(){
|
||||
throw new Error('Method `disable` not define!');
|
||||
}
|
||||
hide(){
|
||||
throw new Error('Method `hide` not define!');
|
||||
}
|
||||
}
|
40
resources/assets/v2/libraries/dark-editable/Modes/InlineMode.js
Executable file
40
resources/assets/v2/libraries/dark-editable/Modes/InlineMode.js
Executable file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* InlineMode.js
|
||||
* Copyright (c) 2024 https://github.com/DarKsandr/dark-editable
|
||||
*
|
||||
* License: MIT
|
||||
*
|
||||
* Copied and slightly edited by James Cole <james@firefly-iii.org>
|
||||
*/
|
||||
|
||||
import BaseMode from "./BaseMode.js";
|
||||
|
||||
export default class InlineMode extends BaseMode{
|
||||
init(){
|
||||
const open = () => {
|
||||
if(!this.context.disabled){
|
||||
const item = this.context.typeElement.create();
|
||||
this.event_show();
|
||||
this.context.element.removeEventListener('click', open);
|
||||
this.context.element.innerHTML = '';
|
||||
this.context.element.append(item);
|
||||
this.event_shown();
|
||||
}
|
||||
}
|
||||
this.context.element.addEventListener('click', open);
|
||||
}
|
||||
enable(){
|
||||
|
||||
}
|
||||
disable(){
|
||||
|
||||
}
|
||||
hide(){
|
||||
this.event_hide();
|
||||
this.context.element.innerHTML = this.context.value;
|
||||
setTimeout(() => {
|
||||
this.init();
|
||||
this.event_hidden();
|
||||
}, 100);
|
||||
}
|
||||
}
|
53
resources/assets/v2/libraries/dark-editable/Modes/PopupMode.js
Executable file
53
resources/assets/v2/libraries/dark-editable/Modes/PopupMode.js
Executable file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* PopupMode.js
|
||||
* Copyright (c) 2024 https://github.com/DarKsandr/dark-editable
|
||||
*
|
||||
* License: MIT
|
||||
*
|
||||
* Copied and slightly edited by James Cole <james@firefly-iii.org>
|
||||
*/
|
||||
|
||||
import BaseMode from "./BaseMode.js";
|
||||
|
||||
export default class PopupMode extends BaseMode{
|
||||
init(){
|
||||
this.popover = new bootstrap.Popover(this.context.element, {
|
||||
container: "body",
|
||||
content: this.context.typeElement.create(),
|
||||
html: true,
|
||||
customClass: "dark-editable",
|
||||
title: this.context.title,
|
||||
});
|
||||
this.context.element.addEventListener('show.bs.popover', () => {
|
||||
this.event_show();
|
||||
});
|
||||
this.context.element.addEventListener('shown.bs.popover', () => {
|
||||
this.event_shown();
|
||||
});
|
||||
this.context.element.addEventListener('hide.bs.popover', () => {
|
||||
this.event_hide();
|
||||
});
|
||||
this.context.element.addEventListener('hidden.bs.popover', () => {
|
||||
this.event_hidden();
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
if(target === this.popover.tip || target === this.context.element) return;
|
||||
let current = target;
|
||||
while(current = current.parentNode){
|
||||
if(current === this.popover.tip) return;
|
||||
}
|
||||
this.hide();
|
||||
})
|
||||
}
|
||||
enable(){
|
||||
this.popover.enable();
|
||||
}
|
||||
disable(){
|
||||
this.popover.disable();
|
||||
}
|
||||
hide(){
|
||||
this.popover.hide();
|
||||
}
|
||||
}
|
255
resources/assets/v2/libraries/dark-editable/Types/BaseType.js
Executable file
255
resources/assets/v2/libraries/dark-editable/Types/BaseType.js
Executable file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* BaseMode.js
|
||||
* Copyright (c) 2024 https://github.com/DarKsandr/dark-editable
|
||||
*
|
||||
* License: MIT
|
||||
*
|
||||
* Copied and slightly edited by James Cole <james@firefly-iii.org>
|
||||
*/
|
||||
|
||||
export default class BaseType {
|
||||
context = null;
|
||||
element = null;
|
||||
error = null;
|
||||
form = null;
|
||||
load = null;
|
||||
buttonGroup = null;
|
||||
buttons = {success: null, cancel: null};
|
||||
|
||||
constructor(context) {
|
||||
if (this.constructor === BaseType) {
|
||||
throw new Error(`It's abstract class`);
|
||||
}
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
create() {
|
||||
throw new Error('Method `create` not define!');
|
||||
}
|
||||
|
||||
createContainer(element) {
|
||||
const div = document.createElement(`div`);
|
||||
|
||||
// original list of elements:
|
||||
this.element = element;
|
||||
this.error = this.createContainerError();
|
||||
this.form = this.createContainerForm();
|
||||
this.load = this.createContainerLoad();
|
||||
this.buttons.success = this.createButtonSuccess();
|
||||
this.buttons.cancel = this.createButtonCancel();
|
||||
|
||||
// create first div, with label and input:
|
||||
const topDiv = document.createElement(`div`);
|
||||
topDiv.classList.add("col-12");
|
||||
|
||||
// create label:
|
||||
const label = document.createElement(`label`);
|
||||
label.classList.add("visually-hidden");
|
||||
label.for = element.id;
|
||||
|
||||
// add label + input to top div:
|
||||
topDiv.append(label, element);
|
||||
|
||||
// create second div, with button group:
|
||||
const bottomDiv = document.createElement(`div`);
|
||||
bottomDiv.classList.add("col-12");
|
||||
|
||||
// create button group:
|
||||
this.buttonGroup = this.createButtonGroup();
|
||||
|
||||
// append buttons to button group:
|
||||
this.buttonGroup.append(this.buttons.success, this.buttons.cancel);
|
||||
bottomDiv.append(this.buttonGroup);
|
||||
|
||||
// append bottom and top div to form:
|
||||
this.form.append(topDiv, bottomDiv);
|
||||
|
||||
//this.form.append(element, this.load, this.buttons.success, this.buttons.cancel);
|
||||
//this.form.append(element, this.load, this.buttonGroup);
|
||||
div.append(this.error, this.form);
|
||||
return div;
|
||||
}
|
||||
|
||||
createButtonGroup() {
|
||||
const div = document.createElement(`div`);
|
||||
div.classList.add("btn-group", "btn-group-sm");
|
||||
return div;
|
||||
}
|
||||
|
||||
createContainerError() {
|
||||
const div = document.createElement(`div`);
|
||||
div.classList.add("text-danger", "fst-italic", "mb-2", "fw-bold");
|
||||
div.style.display = "none";
|
||||
return div;
|
||||
}
|
||||
|
||||
createContainerForm() {
|
||||
|
||||
|
||||
const form = document.createElement(`form`);
|
||||
form.classList.add("row", "row-cols-lg-auto", "g-3", "align-items-center");
|
||||
//form.style.gap = "20px";
|
||||
form.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const newValue = this.getValue();
|
||||
if (this.context.send && this.context.pk && this.context.url && (this.context.value !== newValue)) {
|
||||
this.showLoad();
|
||||
let msg;
|
||||
try {
|
||||
const response = await this.ajax(newValue);
|
||||
if (response.ok) {
|
||||
msg = await this.context.success(response, newValue);
|
||||
} else {
|
||||
msg = await this.context.error(response, newValue) || `${response.status} ${response.statusText}`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
msg = error;
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
this.setError(msg);
|
||||
this.showError();
|
||||
} else {
|
||||
this.setError(null);
|
||||
this.hideError();
|
||||
this.context.value = this.getValue();
|
||||
this.context.modeElement.hide();
|
||||
this.initText();
|
||||
}
|
||||
this.hideLoad();
|
||||
} else {
|
||||
this.context.value = this.getValue();
|
||||
this.context.modeElement.hide();
|
||||
this.initText();
|
||||
}
|
||||
this.context.element.dispatchEvent(new CustomEvent("save"));
|
||||
})
|
||||
return form;
|
||||
}
|
||||
|
||||
createContainerLoad() {
|
||||
const div = document.createElement(`div`);
|
||||
div.style.display = "none";
|
||||
div.style.position = "absolute";
|
||||
div.style.background = "white";
|
||||
div.style.width = "100%";
|
||||
div.style.height = "100%";
|
||||
div.style.top = 0;
|
||||
div.style.left = 0;
|
||||
const loader = document.createElement(`div`);
|
||||
loader.classList.add("dark-editable-loader");
|
||||
div.append(loader);
|
||||
return div;
|
||||
}
|
||||
|
||||
createButton() {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.classList.add("btn", "btn-sm");
|
||||
button.style.color = "transparent";
|
||||
button.style.textShadow = "0 0 0 white";
|
||||
return button;
|
||||
}
|
||||
|
||||
createButtonSuccess() {
|
||||
const btn_success = this.createButton();
|
||||
btn_success.type = "submit";
|
||||
btn_success.classList.add("btn-success");
|
||||
btn_success.innerHTML = "✔";
|
||||
return btn_success;
|
||||
}
|
||||
|
||||
createButtonCancel() {
|
||||
const btn_cancel = this.createButton();
|
||||
btn_cancel.classList.add("btn-danger");
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = "✖";
|
||||
btn_cancel.append(div);
|
||||
btn_cancel.addEventListener("click", () => {
|
||||
this.context.modeElement.hide();
|
||||
});
|
||||
return btn_cancel;
|
||||
}
|
||||
|
||||
hideLoad() {
|
||||
this.load.style.display = "none";
|
||||
}
|
||||
|
||||
showLoad() {
|
||||
this.load.style.display = "block";
|
||||
}
|
||||
|
||||
ajax(new_value) {
|
||||
let url = this.context.url;
|
||||
const form = new FormData;
|
||||
form.append("pk", this.context.pk);
|
||||
form.append("name", this.context.name);
|
||||
form.append("value", new_value);
|
||||
const option = {};
|
||||
option.method = this.context.ajaxOptions.method;
|
||||
if (option.method === "POST") {
|
||||
option.body = form;
|
||||
} else {
|
||||
url += "?" + new URLSearchParams(form).toString();
|
||||
}
|
||||
return fetch(url, option);
|
||||
}
|
||||
|
||||
async successResponse(response, newValue) {
|
||||
|
||||
}
|
||||
|
||||
async errorResponse(response, newValue) {
|
||||
|
||||
}
|
||||
|
||||
setError(errorMsg) {
|
||||
this.error.innerHTML = errorMsg;
|
||||
}
|
||||
|
||||
showError() {
|
||||
this.error.style.display = "block";
|
||||
}
|
||||
|
||||
hideError() {
|
||||
if (this.error) {
|
||||
this.error.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
createElement(name) {
|
||||
const element = document.createElement(name);
|
||||
console.log(element);
|
||||
element.classList.add("form-control");
|
||||
if (this.context.required) {
|
||||
element.required = this.context.required;
|
||||
}
|
||||
this.add_focus(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
add_focus(element) {
|
||||
this.context.element.addEventListener('shown', function () {
|
||||
element.focus();
|
||||
});
|
||||
}
|
||||
|
||||
initText() {
|
||||
if (this.context.value === "") {
|
||||
this.context.element.innerHTML = this.context.emptytext;
|
||||
return true;
|
||||
} else {
|
||||
this.context.element.innerHTML = this.context.value;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
initOptions() {
|
||||
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.element.value;
|
||||
}
|
||||
}
|
16
resources/assets/v2/libraries/dark-editable/Types/DateTimeType.js
Executable file
16
resources/assets/v2/libraries/dark-editable/Types/DateTimeType.js
Executable file
@ -0,0 +1,16 @@
|
||||
import DateType from "./DateType.js";
|
||||
|
||||
export default class DateTimeType extends DateType{
|
||||
create(){
|
||||
const input = this.createElement(`input`);
|
||||
input.type = "datetime-local";
|
||||
|
||||
return this.createContainer(input);
|
||||
}
|
||||
|
||||
initOptions(){
|
||||
this.context.get_opt("format", "YYYY-MM-DD HH:mm");
|
||||
this.context.get_opt("viewformat", "YYYY-MM-DD HH:mm");
|
||||
this.context.value = moment(this.context.value).format("YYYY-MM-DDTHH:mm");
|
||||
}
|
||||
}
|
25
resources/assets/v2/libraries/dark-editable/Types/DateType.js
Executable file
25
resources/assets/v2/libraries/dark-editable/Types/DateType.js
Executable file
@ -0,0 +1,25 @@
|
||||
import BaseType from "./BaseType.js";
|
||||
|
||||
export default class DateType extends BaseType{
|
||||
create(){
|
||||
const input = this.createElement(`input`);
|
||||
input.type = "date";
|
||||
|
||||
return this.createContainer(input);
|
||||
}
|
||||
|
||||
initText(){
|
||||
if(this.value === ""){
|
||||
this.context.element.innerHTML = this.context.emptytext;
|
||||
return true;
|
||||
} else {
|
||||
this.context.element.innerHTML = moment(this.context.value).format(this.context.viewformat);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
initOptions(){
|
||||
this.context.get_opt("format", "YYYY-MM-DD");
|
||||
this.context.get_opt("viewformat", "YYYY-MM-DD");
|
||||
}
|
||||
}
|
19
resources/assets/v2/libraries/dark-editable/Types/InputType.js
Executable file
19
resources/assets/v2/libraries/dark-editable/Types/InputType.js
Executable file
@ -0,0 +1,19 @@
|
||||
import BaseType from "./BaseType.js";
|
||||
|
||||
export default class InputType extends BaseType{
|
||||
create(){
|
||||
// expand input element with necessary classes and things.
|
||||
// <input type="text" class="form-control form-control-md" id="inlineFormInputGroupUsername" placeholder="Username">
|
||||
|
||||
|
||||
const input = this.createElement(`input`);
|
||||
const id = this.context.element.id + '_input';
|
||||
input.type = this.context.type;
|
||||
input.id = id;
|
||||
input.autocomplete = 'off';
|
||||
input.placeholder = this.context.element.innerText;
|
||||
input.classList.add("form-control", "form-control-md");
|
||||
|
||||
return this.createContainer(input);
|
||||
}
|
||||
}
|
36
resources/assets/v2/libraries/dark-editable/Types/SelectType.js
Executable file
36
resources/assets/v2/libraries/dark-editable/Types/SelectType.js
Executable file
@ -0,0 +1,36 @@
|
||||
import BaseType from "./BaseType.js";
|
||||
|
||||
export default class SelectType extends BaseType{
|
||||
create(){
|
||||
const select = this.createElement(`select`);
|
||||
this.context.source.forEach(item => {
|
||||
const opt = document.createElement(`option`);
|
||||
opt.value = item.value;
|
||||
opt.innerHTML = item.text;
|
||||
select.append(opt);
|
||||
});
|
||||
|
||||
return this.createContainer(select);
|
||||
}
|
||||
|
||||
initText(){
|
||||
this.context.element.innerHTML = this.context.emptytext;
|
||||
if(this.context.value !== "" && this.context.source.length > 0){
|
||||
for(const key in this.context.source){
|
||||
const item = this.context.source[ key ];
|
||||
if(item.value == this.context.value){
|
||||
this.context.element.innerHTML = item.text;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
initOptions(){
|
||||
this.context.get_opt("source", []);
|
||||
if(typeof this.context.source === "string" && this.context.source !== ""){
|
||||
this.context.source = JSON.parse(this.context.source);
|
||||
}
|
||||
}
|
||||
}
|
9
resources/assets/v2/libraries/dark-editable/Types/TextAreaType.js
Executable file
9
resources/assets/v2/libraries/dark-editable/Types/TextAreaType.js
Executable file
@ -0,0 +1,9 @@
|
||||
import BaseType from "./BaseType.js";
|
||||
|
||||
export default class TextAreaType extends BaseType{
|
||||
create(){
|
||||
const textarea = this.createElement(`textarea`);
|
||||
|
||||
return this.createContainer(textarea);
|
||||
}
|
||||
}
|
87
resources/assets/v2/libraries/dark-editable/dark-editable.css
Executable file
87
resources/assets/v2/libraries/dark-editable/dark-editable.css
Executable file
@ -0,0 +1,87 @@
|
||||
.dark-editable-element{
|
||||
border-bottom: dashed 1px #0088cc;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark-editable-element-disabled{
|
||||
border-bottom: none;
|
||||
cursor: default;
|
||||
}
|
||||
.dark-editable-element-empty{
|
||||
font-style: italic;
|
||||
color: #DD1144;
|
||||
}
|
||||
.dark-editable{
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.dark-editable-loader {
|
||||
font-size: 5px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
-webkit-animation: load5 1.1s infinite ease;
|
||||
animation: load5 1.1s infinite ease;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
@-webkit-keyframes load5 {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0em -2.6em 0em 0em #000000, 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.5), -1.8em -1.8em 0 0em rgba(0,0,0, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.7), 1.8em -1.8em 0 0em #000000, 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.5);
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.5), 1.8em -1.8em 0 0em rgba(0,0,0, 0.7), 2.5em 0em 0 0em #000000, 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.5), 2.5em 0em 0 0em rgba(0,0,0, 0.7), 1.75em 1.75em 0 0em #000000, 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.5), 1.75em 1.75em 0 0em rgba(0,0,0, 0.7), 0em 2.5em 0 0em #000000, -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.5), 0em 2.5em 0 0em rgba(0,0,0, 0.7), -1.8em 1.8em 0 0em #000000, -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.5), -1.8em 1.8em 0 0em rgba(0,0,0, 0.7), -2.6em 0em 0 0em #000000, -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.5), -2.6em 0em 0 0em rgba(0,0,0, 0.7), -1.8em -1.8em 0 0em #000000;
|
||||
}
|
||||
}
|
||||
@keyframes load5 {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0em -2.6em 0em 0em #000000, 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.5), -1.8em -1.8em 0 0em rgba(0,0,0, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.7), 1.8em -1.8em 0 0em #000000, 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.5);
|
||||
}
|
||||
25% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.5), 1.8em -1.8em 0 0em rgba(0,0,0, 0.7), 2.5em 0em 0 0em #000000, 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.5), 2.5em 0em 0 0em rgba(0,0,0, 0.7), 1.75em 1.75em 0 0em #000000, 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.5), 1.75em 1.75em 0 0em rgba(0,0,0, 0.7), 0em 2.5em 0 0em #000000, -1.8em 1.8em 0 0em rgba(0,0,0, 0.2), -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.5), 0em 2.5em 0 0em rgba(0,0,0, 0.7), -1.8em 1.8em 0 0em #000000, -2.6em 0em 0 0em rgba(0,0,0, 0.2), -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
75% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.5), -1.8em 1.8em 0 0em rgba(0,0,0, 0.7), -2.6em 0em 0 0em #000000, -1.8em -1.8em 0 0em rgba(0,0,0, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
box-shadow: 0em -2.6em 0em 0em rgba(0,0,0, 0.2), 1.8em -1.8em 0 0em rgba(0,0,0, 0.2), 2.5em 0em 0 0em rgba(0,0,0, 0.2), 1.75em 1.75em 0 0em rgba(0,0,0, 0.2), 0em 2.5em 0 0em rgba(0,0,0, 0.2), -1.8em 1.8em 0 0em rgba(0,0,0, 0.5), -2.6em 0em 0 0em rgba(0,0,0, 0.7), -1.8em -1.8em 0 0em #000000;
|
||||
}
|
||||
}
|
||||
|
181
resources/assets/v2/libraries/dark-editable/dark-editable.js
Normal file
181
resources/assets/v2/libraries/dark-editable/dark-editable.js
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* dark-editable.js
|
||||
* Copyright (c) 2024 https://github.com/DarKsandr/dark-editable
|
||||
*
|
||||
* License: MIT
|
||||
*
|
||||
* Copied and slightly edited by James Cole <james@firefly-iii.org>
|
||||
*/
|
||||
|
||||
import "./dark-editable.css";
|
||||
import PopupMode from "./Modes/PopupMode.js";
|
||||
import InlineMode from "./Modes/InlineMode.js";
|
||||
import BaseType from "./Types/BaseType.js";
|
||||
import InputType from "./Types/InputType.js";
|
||||
import TextAreaType from "./Types/TextAreaType.js";
|
||||
import SelectType from "./Types/SelectType.js";
|
||||
import DateType from "./Types/DateType.js";
|
||||
import DateTimeType from "./Types/DateTimeType.js";
|
||||
|
||||
export default class DarkEditable{
|
||||
modeElement = null;
|
||||
typeElement = null;
|
||||
mode = null;
|
||||
type = null;
|
||||
emptytext = null;
|
||||
viewformat = null;
|
||||
pk = null;
|
||||
name = null;
|
||||
|
||||
constructor(element, options = {}){
|
||||
this.element = element;
|
||||
this.options = options;
|
||||
|
||||
this.init_options();
|
||||
this.typeElement = this.route_type();
|
||||
this.typeElement.initOptions();
|
||||
this.modeElement = this.route_mode();
|
||||
this.modeElement.init();
|
||||
this.init_text();
|
||||
this.init_style();
|
||||
if(this.disabled){
|
||||
this.disable();
|
||||
}
|
||||
this.element.dispatchEvent(new CustomEvent("init"));
|
||||
}
|
||||
|
||||
/* INIT METHODS */
|
||||
|
||||
get_opt(name, default_value){
|
||||
return this[ name ] = this.element.dataset?.[ name ] ?? this.options?.[ name ] ?? default_value;
|
||||
}
|
||||
get_opt_bool(name, default_value){
|
||||
this.get_opt(name, default_value);
|
||||
if(typeof this[ name ] !== "boolean"){
|
||||
if(this[ name ] === "true") {
|
||||
this[ name ] = true;
|
||||
} else if(this[ name ] === "false") {
|
||||
this[ name ] = false;
|
||||
} else {
|
||||
this[ name ] = default_value;
|
||||
}
|
||||
}
|
||||
return this[ name ];
|
||||
}
|
||||
|
||||
init_options(){
|
||||
//priority date elements
|
||||
this.get_opt("value", this.element.innerHTML);
|
||||
this.get_opt("name", this.element.id);
|
||||
this.get_opt("pk", null);
|
||||
this.get_opt("title", "");
|
||||
this.get_opt("type", "text");
|
||||
this.get_opt("emptytext", "Empty");
|
||||
this.get_opt("mode", "popup");
|
||||
this.get_opt("url", null);
|
||||
this.get_opt("ajaxOptions", {});
|
||||
this.ajaxOptions = Object.assign({
|
||||
method: "POST",
|
||||
dataType: "text",
|
||||
}, this.ajaxOptions);
|
||||
this.get_opt_bool("send", true);
|
||||
this.get_opt_bool("disabled", false);
|
||||
this.get_opt_bool("required", false);
|
||||
if(this.options?.success && typeof this.options?.success == "function"){
|
||||
this.success = this.options.success;
|
||||
}
|
||||
if(this.options?.error && typeof this.options?.error == "function"){
|
||||
this.error = this.options.error;
|
||||
}
|
||||
}
|
||||
|
||||
init_text(){
|
||||
const empty_class = "dark-editable-element-empty";
|
||||
this.element.classList.remove(empty_class);
|
||||
if(this.typeElement.initText()){
|
||||
this.element.classList.add(empty_class);
|
||||
}
|
||||
}
|
||||
|
||||
init_style(){
|
||||
this.element.classList.add("dark-editable-element");
|
||||
}
|
||||
|
||||
/* INIT METHODS END */
|
||||
route_mode(){
|
||||
switch (this.mode){
|
||||
default:
|
||||
throw new Error(`Mode ${this.mode} not found!`)
|
||||
case 'popup':
|
||||
return new PopupMode(this);
|
||||
case 'inline':
|
||||
return new InlineMode(this);
|
||||
}
|
||||
}
|
||||
|
||||
route_type(){
|
||||
if(this.type.prototype instanceof BaseType){
|
||||
return new this.type(this);
|
||||
}
|
||||
if(typeof this.type === 'string'){
|
||||
switch(this.type){
|
||||
case "text":
|
||||
case "password":
|
||||
case "email":
|
||||
case "url":
|
||||
case "tel":
|
||||
case "number":
|
||||
case "range":
|
||||
case "time":
|
||||
return new InputType(this);
|
||||
case "textarea":
|
||||
return new TextAreaType(this);
|
||||
case "select":
|
||||
return new SelectType(this);
|
||||
case "date":
|
||||
return new DateType(this);
|
||||
case "datetime":
|
||||
return new DateTimeType(this);
|
||||
}
|
||||
}
|
||||
throw new Error(`Undefined type`);
|
||||
}
|
||||
|
||||
/* AJAX */
|
||||
|
||||
async success(response, newValue){
|
||||
return await this.typeElement.successResponse(response, newValue);
|
||||
}
|
||||
|
||||
async error(response, newValue){
|
||||
return await this.typeElement.errorResponse(response, newValue);
|
||||
}
|
||||
|
||||
/* AJAX END */
|
||||
|
||||
/* METHODS */
|
||||
|
||||
enable(){
|
||||
this.disabled = false;
|
||||
this.element.classList.remove("dark-editable-element-disabled");
|
||||
this.modeElement.enable();
|
||||
|
||||
}
|
||||
|
||||
disable(){
|
||||
this.disabled = true;
|
||||
this.element.classList.add("dark-editable-element-disabled");
|
||||
this.modeElement.enable();
|
||||
}
|
||||
|
||||
setValue(value){
|
||||
this.value = value;
|
||||
this.init_text();
|
||||
}
|
||||
|
||||
getValue(){
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/* METHODS END */
|
||||
}
|
@ -349,7 +349,7 @@ let transactions = function () {
|
||||
this.detectTransactionType();
|
||||
|
||||
// parse transaction:
|
||||
let transactions = parseFromEntries(this.entries, this.groupProperties.transactionType);
|
||||
let transactions = parseFromEntries(this.entries, null, this.groupProperties.transactionType);
|
||||
let submission = {
|
||||
group_title: this.groupProperties.title,
|
||||
fire_webhooks: this.formStates.webhooksButton,
|
||||
@ -466,7 +466,7 @@ let transactions = function () {
|
||||
// onRenderItem: renderAccount,
|
||||
onChange: changeSourceAccount,
|
||||
onSelectItem: selectSourceAccount,
|
||||
hiddenValue: this.items[count].source_account.alpine_name,
|
||||
hiddenValue: this.entries[count].source_account.alpine_name
|
||||
});
|
||||
addAutocomplete({
|
||||
selector: 'input.ac-dest',
|
||||
|
@ -58,6 +58,7 @@ let transactions = function () {
|
||||
return {
|
||||
// transactions are stored in "entries":
|
||||
entries: [],
|
||||
originals: [],
|
||||
|
||||
// state of the form is stored in formState:
|
||||
formStates: {
|
||||
@ -126,7 +127,7 @@ let transactions = function () {
|
||||
this.formStates.isSubmitting = true;
|
||||
|
||||
// parse transaction:
|
||||
let transactions = parseFromEntries(this.entries, this.groupProperties.transactionType);
|
||||
let transactions = parseFromEntries(this.entries, this.originals, this.groupProperties.transactionType);
|
||||
let submission = {
|
||||
group_title: this.groupProperties.title,
|
||||
fire_webhooks: this.formStates.webhooksButton,
|
||||
@ -263,7 +264,7 @@ let transactions = function () {
|
||||
getter.show(groupId, {}).then((response) => {
|
||||
const data = response.data.data;
|
||||
this.groupProperties.id = parseInt(data.id);
|
||||
this.groupProperties.transactionType = data.attributes.transactions[0].type;
|
||||
this.groupProperties.transactionType = data.attributes.transactions[0].type.toLowerCase();
|
||||
this.groupProperties.title = data.attributes.title ?? data.attributes.transactions[0].description;
|
||||
this.entries = parseDownloadedSplits(data.attributes.transactions);
|
||||
|
||||
|
@ -43,12 +43,14 @@ function addPointToMap(e) {
|
||||
markers[index].on('dragend', dragEnd);
|
||||
markers[index].addTo(maps[index]);
|
||||
|
||||
const setEvent = new CustomEvent('location-set', {detail: {
|
||||
const setEvent = new CustomEvent('location-set', {
|
||||
detail: {
|
||||
latitude: e.latlng.lat,
|
||||
longitude: e.latlng.lng,
|
||||
index: index,
|
||||
zoomLevel: maps[index].getZoom()
|
||||
}});
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(setEvent);
|
||||
}
|
||||
}
|
||||
@ -56,10 +58,12 @@ function addPointToMap(e) {
|
||||
function saveZoomOfMap(e) {
|
||||
//let index = parseInt(e.sourceTarget._container.attributes['data-index'].value);
|
||||
let index = 0;
|
||||
const zoomEvent = new CustomEvent('location-zoom', {detail: {
|
||||
const zoomEvent = new CustomEvent('location-zoom', {
|
||||
detail: {
|
||||
index: index,
|
||||
zoomLevel: maps[index].getZoom()
|
||||
}});
|
||||
}
|
||||
});
|
||||
document.dispatchEvent(zoomEvent);
|
||||
}
|
||||
|
||||
@ -87,13 +91,15 @@ export function addLocation(index) {
|
||||
|
||||
//let holder = document.getElementById('location_map_' + index);
|
||||
let holder = document.getElementById('location_map');
|
||||
maps[index] = L.map(holder).setView([holder.dataset.latitude, holder.dataset.longitude], holder.dataset.zoomLevel);
|
||||
if (holder) {
|
||||
maps[index] = L.map(holder).setView([holder.dataset.latitude, holder.dataset.longitude], holder.dataset.zoomLevel);
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(maps[index]);
|
||||
maps[index].on('click', addPointToMap);
|
||||
maps[index].on('zoomend', saveZoomOfMap);
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(maps[index]);
|
||||
maps[index].on('click', addPointToMap);
|
||||
maps[index].on('zoomend', saveZoomOfMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,15 +21,23 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function parseFromEntries(entries, transactionType) {
|
||||
export function parseFromEntries(entries, originals, transactionType) {
|
||||
let returnArray = [];
|
||||
for (let i in entries) {
|
||||
if (entries.hasOwnProperty(i)) {
|
||||
const entry = entries[i];
|
||||
let compare = false;
|
||||
let original = {};
|
||||
if (originals !== null && originals.hasOwnProperty(i)) {
|
||||
compare = true;
|
||||
let original = originals[i];
|
||||
}
|
||||
let current = {};
|
||||
|
||||
// fields for transaction
|
||||
current.description = entry.description;
|
||||
if ((compare && original.description !== entry.description) || !compare) {
|
||||
current.description = entry.description;
|
||||
}
|
||||
|
||||
// source and destination
|
||||
current.source_name = entry.source_account.name;
|
||||
|
@ -25,6 +25,8 @@ import Get from "../../api/v2/model/transaction/get.js";
|
||||
import {parseDownloadedSplits} from "./shared/parse-downloaded-splits.js";
|
||||
import {format} from "date-fns";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
import DarkEditable from "../../libraries/dark-editable/dark-editable.js";
|
||||
|
||||
|
||||
let show = function () {
|
||||
return {
|
||||
@ -56,6 +58,10 @@ let show = function () {
|
||||
|
||||
pageProperties: {},
|
||||
formatMoney(amount, currencyCode) {
|
||||
console.log('formatting', amount, currencyCode);
|
||||
if('' === currencyCode) {
|
||||
currencyCode = 'EUR';
|
||||
}
|
||||
return formatMoney(amount, currencyCode);
|
||||
},
|
||||
format(date) {
|
||||
@ -95,6 +101,12 @@ let show = function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at this point do the inline change fields
|
||||
//inlineEdit('journal_description')
|
||||
const usernameEl = document.getElementById('journal_description');
|
||||
const popover = new DarkEditable(usernameEl, {mode: 'inline', url: '/something-else'});
|
||||
|
||||
}).catch((error) => {
|
||||
// todo auto generated.
|
||||
this.notifications.error.show = true;
|
||||
|
@ -45,6 +45,16 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<a class="btn btn-primary" :href="'./transactions/edit/' + groupProperties.id">
|
||||
<em class="fa-solid fa-edit"></em> {{ __('firefly.edit') }}
|
||||
</a>
|
||||
<a class="btn btn-danger" :href="'./transactions/delete/' + groupProperties.id">
|
||||
<em class="fa-solid fa-trash"></em> {{ __('firefly.delete') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
@ -123,10 +133,17 @@
|
||||
<div class="row">
|
||||
<template x-for="(entry, index) in entries">
|
||||
<div class="col-xl-4 col-lg-6 col-md-12 col-sm-12 col-xs-12">
|
||||
|
||||
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
<span x-text="entry.description"></span>
|
||||
<span
|
||||
id="journal_description"
|
||||
data-type="text"
|
||||
data-pk="12"
|
||||
data-title="{{ __('firefly.description') }}" x-text="entry.description"></span>
|
||||
<template x-if="entries.length > 1">
|
||||
<span class="badge bg-secondary">
|
||||
<span x-text="index + 1"></span> / <span x-text="entries.length"></span>
|
||||
@ -215,7 +232,7 @@
|
||||
</template>
|
||||
</template>
|
||||
<template x-for="meta in metaFields">
|
||||
<template x-if="null !== entry[meta] and '' !== entry[meta]">
|
||||
<template x-if="typeof entry[meta] !== 'undefined' && null !== entry[meta] && '' !== entry[meta]">
|
||||
<tr>
|
||||
<th><span x-text="meta"></span></th>
|
||||
<td><span x-text="entry[meta]"></span></td>
|
||||
@ -224,7 +241,7 @@
|
||||
</template>
|
||||
<tr>
|
||||
<th>recurring things</th>
|
||||
<td>TODO meta</td>
|
||||
<td>TODO recurring</td>
|
||||
</tr>
|
||||
<template x-if="entry.tags.length > 0">
|
||||
<tr>
|
||||
|
@ -55,6 +55,12 @@ final class BillDateCalculatorTest extends TestCase
|
||||
// already paid on the 12th, expect it next month.
|
||||
'1Mc' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => Carbon::parse('2023-11-12'), 'expected' => ['2023-12-01']],
|
||||
|
||||
// every month, start on 2024-01-30, view is quarterly
|
||||
'1Md' => ['earliest' => Carbon::parse('2023-01-01'), 'latest' => Carbon::parse('2023-03-31'), 'billStart' => Carbon::parse('2023-01-29'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => null, 'expected' => ['2023-01-29', '2023-02-28', '2023-03-29']],
|
||||
|
||||
// every month, start on 2024-01-30, view is quarterly
|
||||
'1Me' => ['earliest' => Carbon::parse('2024-01-01'), 'latest' => Carbon::parse('2024-03-31'), 'billStart' => Carbon::parse('2023-01-30'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => null, 'expected' => ['2024-01-30', '2024-02-29', '2024-03-30']],
|
||||
|
||||
// yearly not due this month. Should jump to next year.
|
||||
'1Ya' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2021-05-01'), 'period' => 'yearly', 'skip' => 0, 'lastPaid' => Carbon::parse('2023-05-02'), 'expected' => ['2024-05-01']],
|
||||
];
|
||||
|
Loading…
Reference in New Issue
Block a user