mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2026-03-03 03:07:01 +00:00
Updates for rejection
This commit is contained in:
82
resources/js/clients/quotes/reject.js
vendored
Normal file
82
resources/js/clients/quotes/reject.js
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
class Reject {
|
||||
constructor() {
|
||||
// Always show rejection modal for confirmation
|
||||
// (Siempre mostrar modal de rechazo para confirmación)
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
document.getElementById('reject-form').submit();
|
||||
}
|
||||
|
||||
displayRejectModal() {
|
||||
let displayRejectModal = document.getElementById("displayRejectModal");
|
||||
if (displayRejectModal) {
|
||||
displayRejectModal.removeAttribute("style");
|
||||
}
|
||||
}
|
||||
|
||||
hideRejectModal() {
|
||||
let displayRejectModal = document.getElementById("displayRejectModal");
|
||||
if (displayRejectModal) {
|
||||
displayRejectModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
handle() {
|
||||
const rejectButton = document.getElementById('reject-button');
|
||||
if (!rejectButton) return;
|
||||
|
||||
rejectButton.addEventListener('click', () => {
|
||||
rejectButton.disabled = true;
|
||||
|
||||
// Re-enable the reject button after 2 seconds (Rehabilitar botón de rechazo después de 2 segundos)
|
||||
setTimeout(() => {
|
||||
rejectButton.disabled = false;
|
||||
}, 2000);
|
||||
|
||||
// Always display the rejection modal for confirmation
|
||||
// (Siempre mostrar el modal de rechazo para confirmación)
|
||||
this.displayRejectModal();
|
||||
});
|
||||
|
||||
// Handle confirm rejection button (Manejar botón de confirmar rechazo)
|
||||
const rejectConfirmButton = document.getElementById('reject-confirm-button');
|
||||
if (rejectConfirmButton) {
|
||||
rejectConfirmButton.addEventListener('click', () => {
|
||||
const rejectReason = document.getElementById('reject_reason');
|
||||
|
||||
// Set user input value if provided (optional)
|
||||
// (Establecer valor de entrada del usuario si se proporciona - opcional)
|
||||
if (rejectReason) {
|
||||
const userInputField = document.querySelector('#reject-form input[name="user_input"]');
|
||||
if (userInputField) {
|
||||
userInputField.value = rejectReason.value;
|
||||
}
|
||||
}
|
||||
|
||||
this.hideRejectModal();
|
||||
this.submitForm();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle close/cancel button (Manejar botón de cerrar/cancelar)
|
||||
const rejectCloseButton = document.getElementById('reject-close-button');
|
||||
if (rejectCloseButton) {
|
||||
rejectCloseButton.addEventListener('click', () => {
|
||||
this.hideRejectModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Reject().handle();
|
||||
@@ -1,33 +1,47 @@
|
||||
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="approve-form">
|
||||
@csrf
|
||||
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<input type="hidden" name="action" value="approve" id="quote-action">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
<input type="hidden" name="signature">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
</form>
|
||||
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="reject-form">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
</form>
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.approve') }}
|
||||
{{ ctrans('texts.approve') }} / {{ ctrans('texts.reject') }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||
@yield('quote-not-approved-right-side')
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<input type="hidden" name="action" value="approve">
|
||||
<button onclick="setTimeout(() => this.disabled = true, 0); return true;" type="button"
|
||||
<div class="inline-flex rounded-md shadow-sm mr-2">
|
||||
<button type="button"
|
||||
onclick="document.querySelector('#approve-form [name=action]').value='approve'; setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary"
|
||||
id="approve-button">{{ ctrans('texts.approve') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="inline-flex rounded-md shadow-sm ">
|
||||
<button type="button"
|
||||
onclick="document.querySelector('#reject-form [name=action]').value='reject'; setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-secondary bg-red-500 text-white hover:bg-red-600"
|
||||
id="reject-button">{{ ctrans('texts.reject') }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{{-- Rejection Confirmation Modal (Modal de confirmación de rechazo) --}}
|
||||
<div style="display: none;" id="displayRejectModal" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center z-50">
|
||||
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity">
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
||||
<h3 class="text-xl leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.reject_quote') }}
|
||||
</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
{{ ctrans('texts.reject_quote_confirmation') }}
|
||||
</p>
|
||||
|
||||
<div class="mt-4">
|
||||
<label for="reject_reason" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
{{ ctrans('texts.reason') }} ({{ ctrans('texts.optional') }})
|
||||
</label>
|
||||
<textarea
|
||||
name="reject_reason"
|
||||
id="reject_reason"
|
||||
rows="3"
|
||||
class="block w-full rounded-md border-gray-300 bg-gray-100 focus:border-primary-300 focus:bg-white focus:ring focus:ring-primary-200 focus:ring-opacity-50"
|
||||
placeholder="{{ ctrans('texts.enter_reason') }}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
|
||||
<div class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
|
||||
<button type="button" id="reject-confirm-button" class="button button-danger bg-red-500 hover:bg-red-600 text-white w-full sm:w-auto">
|
||||
{{ ctrans('texts.reject') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
|
||||
<button type="button" class="button button-secondary w-full sm:w-auto" id="reject-close-button">
|
||||
{{ ctrans('texts.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary" name="action"
|
||||
value="approve">{{ ctrans('texts.approve') }}</button>
|
||||
|
||||
<button type="submit" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="button button-primary bg-primary" name="action"
|
||||
value="reject">{{ ctrans('texts.reject') }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
90
resources/views/portal/ninja2020/quotes/reject.blade.php
Normal file
90
resources/views/portal/ninja2020/quotes/reject.blade.php
Normal file
@@ -0,0 +1,90 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.reject'))
|
||||
|
||||
@push('head')
|
||||
<meta name="accept-user-input" content="true">
|
||||
@endpush
|
||||
|
||||
@section('body')
|
||||
<form action="{{ route('client.quotes.bulk') }}" method="post" id="reject-form">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="reject">
|
||||
<input type="hidden" name="process" value="true">
|
||||
<input type="hidden" name="user_input" value="">
|
||||
|
||||
@foreach($quotes as $quote)
|
||||
<input type="hidden" name="quotes[]" value="{{ $quote->hashed_id }}">
|
||||
@endforeach
|
||||
</form>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
<div class="flex justify-end">
|
||||
<div class="flex justify-end mb-2">
|
||||
<div class="relative inline-block text-left">
|
||||
<div>
|
||||
<div class="rounded-md shadow-sm">
|
||||
<button type="button" id="reject-button" onclick="setTimeout(() => this.disabled = true, 0); return true;"
|
||||
class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:ring-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">
|
||||
{{ ctrans('texts.reject') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@foreach($quotes as $quote)
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.quote') }}
|
||||
<a class="button-link text-primary" href="{{ route('client.quote.show', $quote->hashed_id) }}">
|
||||
({{ $quote->number }})
|
||||
</a>
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500" translate>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<dl>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.quote_number') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $quote->number }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.quote_date') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ $quote->translateDate($quote->date, $quote->client->date_format(), $quote->client->locale()) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($quote->amount, $quote->client) }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
@include('portal.ninja2020.quotes.includes.user-input')
|
||||
@endsection
|
||||
|
||||
@push('footer')
|
||||
@vite('resources/js/clients/quotes/reject.js')
|
||||
@endpush
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@section('body')
|
||||
|
||||
@if(!$quote->isApproved() && $client->getSetting('custom_message_unapproved_quote'))
|
||||
@if(!$quote->isApproved() && !$quote->isRejected() && $client->getSetting('custom_message_unapproved_quote'))
|
||||
@component('portal.ninja2020.components.message')
|
||||
<pre>{{ $client->getSetting('custom_message_unapproved_quote') }}</pre>
|
||||
@endcomponent
|
||||
@@ -31,13 +31,13 @@
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@if($quote->invoice()->exists())
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 flex justify-end">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<a class="button button-primary bg-primary" href="/client/invoices/{{ $quote->invoice->hashed_id }}">{{ ctrans('texts.view_invoice') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if($quote->invoice()->exists())
|
||||
<div class="mt-5 sm:mt-0 sm:ml-6 flex justify-end">
|
||||
<div class="inline-flex rounded-md shadow-sm">
|
||||
<a class="button button-primary bg-primary" href="/client/invoices/{{ $quote->invoice->hashed_id }}">{{ ctrans('texts.view_invoice') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,7 +55,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@elseif($quote->status_id == \App\Models\Quote::STATUS_REJECTED)
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.rejected') }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||
@@ -78,13 +90,14 @@
|
||||
|
||||
@section('footer')
|
||||
@include('portal.ninja2020.quotes.includes.user-input')
|
||||
@include('portal.ninja2020.quotes.includes.reject-input')
|
||||
@include('portal.ninja2020.invoices.includes.terms', ['entities' => [$quote], 'variables' => $variables, 'entity_type' => ctrans('texts.quote')])
|
||||
@include('portal.ninja2020.invoices.includes.signature')
|
||||
@endsection
|
||||
|
||||
@push('head')
|
||||
@vite('resources/js/clients/quotes/approve.js')
|
||||
|
||||
@vite('resources/js/clients/quotes/reject.js')
|
||||
<script type="text/javascript" defer>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
Reference in New Issue
Block a user