Booking product type added

This commit is contained in:
Jitendra Singh 2020-02-14 11:40:12 +05:30
parent 4f38211b71
commit fa53b72058
80 changed files with 3994 additions and 2 deletions

View File

@ -101,7 +101,8 @@
"Webkul\\CartRule\\": "packages/Webkul/CartRule/src", "Webkul\\CartRule\\": "packages/Webkul/CartRule/src",
"Webkul\\Rule\\": "packages/Webkul/Rule/src", "Webkul\\Rule\\": "packages/Webkul/Rule/src",
"Webkul\\CMS\\": "packages/Webkul/CMS/src", "Webkul\\CMS\\": "packages/Webkul/CMS/src",
"Webkul\\Velocity\\": "packages/Webkul/Velocity/src" "Webkul\\Velocity\\": "packages/Webkul/Velocity/src",
"Webkul\\BookingProduct\\": "packages/Webkul/BookingProduct/src"
} }
}, },

View File

@ -265,6 +265,7 @@ return [
Webkul\Rule\Providers\RuleServiceProvider::class, Webkul\Rule\Providers\RuleServiceProvider::class,
Webkul\CMS\Providers\CMSServiceProvider::class, Webkul\CMS\Providers\CMSServiceProvider::class,
Webkul\Velocity\Providers\VelocityServiceProvider::class, Webkul\Velocity\Providers\VelocityServiceProvider::class,
Webkul\BookingProduct\Providers\BookingProductServiceProvider::class,
], ],
/* /*

View File

@ -21,6 +21,7 @@ return [
\Webkul\User\Providers\ModuleServiceProvider::class, \Webkul\User\Providers\ModuleServiceProvider::class,
\Webkul\CatalogRule\Providers\ModuleServiceProvider::class, \Webkul\CatalogRule\Providers\ModuleServiceProvider::class,
\Webkul\CartRule\Providers\ModuleServiceProvider::class, \Webkul\CartRule\Providers\ModuleServiceProvider::class,
\Webkul\CMS\Providers\ModuleServiceProvider::class \Webkul\CMS\Providers\ModuleServiceProvider::class,
\Webkul\BookingProduct\Providers\ModuleServiceProvider::class
] ]
]; ];

3
packages/Webkul/BookingProduct/.gitignore vendored Executable file
View File

@ -0,0 +1,3 @@
/node_modules
/package-lock.json
npm-debug.log

View File

@ -0,0 +1,22 @@
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch-poll": "cross-env npm run watch -- --watch-poll --progress",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.19.0",
"cross-env": "^6.0.3",
"jquery": "^3.4.1",
"laravel-mix": "^5.0.0",
"laravel-mix-merge-manifest": "^0.1.2",
"sass": "^1.25.0",
"sass-loader": "^8.0.2",
"vue": "^2.6.10"
}
}

View File

@ -0,0 +1 @@
.bp-location-icon{background-image:url(../images/location.svg);width:32px;height:32px}.bp-slot-icon{background-image:url(../images/slot.svg);width:32px;height:32px}.bp-phone-icon{background-image:url(../images/phone.svg);width:32px;height:32px}.booking-information{margin-bottom:15px;border-top:1px solid #e8e8e8;padding-top:15px}.booking-information .booking-info-row{padding-left:32px;margin-bottom:20px;position:relative}.booking-information .booking-info-row .icon{position:absolute;left:0;top:-4px}.booking-information .booking-info-row .title{color:#5e5e5e;display:block;margin-bottom:5px}.booking-information .booking-info-row .value{display:block;margin-bottom:5px}.booking-information .booking-info-row .value .text-danger{color:#ff5656}.booking-information .booking-info-row .toggle{color:#0041ff;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.booking-information .booking-info-row .toggle .icon{position:relative;width:10px;height:10px;margin-left:5px;top:0}.booking-information .booking-info-row .toggle .icon.arrow-down-icon{background-image:url(../images/arrow-down.svg)!important}.booking-information .booking-info-row .toggle .icon.arrow-up-icon{background-image:url(../images/arrow-up.svg)!important}.booking-information .booking-info-row .days-availability table{margin-top:10px;border-collapse:collapse}.booking-information .booking-info-row .days-availability table tr td{padding:5px;vertical-align:top}.booking-information .booking-info-row .days-availability table tr td:first-child{padding-left:0}.booking-information .booking-info-row .days-availability table tr td:last-child{font-size:14px;padding-left:15px;padding-right:0;color:#5e5e5e}.booking-information .booking-info-row .days-availability table tr td .text-danger{color:#ff5656}.booking-information .book-slots{padding-top:25px;display:inline-block;width:100%;border-top:1px solid #e8e8e8}.booking-information .book-slots h3{font-weight:600;font-size:16px;color:#242424;margin-top:0}.booking-information .book-slots label{color:#3a3a3a}.booking-information .book-slots .control-group label{font-size:16px}.booking-information .book-slots .control-group .radio{display:inline-block}.booking-information .book-slots .control-group-container{width:100%;float:left}.booking-information .book-slots .control-group-container .control-group:not(.quantity){width:50%;float:left}.booking-information .book-slots .control-group-container .control-group:not(.quantity) .control{width:100%}.booking-information .book-slots .control-group-container .control-group:not(.quantity).date{padding-right:5px}.booking-information .book-slots .control-group-container .control-group:not(.quantity).date:after{position:absolute;top:14px;right:10px}.booking-information .book-slots .control-group-container .control-group:not(.quantity).slots:first-child{padding-right:5px}.booking-information .book-slots .control-group-container .control-group:not(.quantity).slots:last-child{padding-left:5px}.booking-information .book-slots .ticket-list .ticket-item{width:100%;display:inline-block;padding:16px 0;border-bottom:1px solid #e8e8e8}.booking-information .book-slots .ticket-list .ticket-item .ticket-info{width:50%;float:left}.booking-information .book-slots .ticket-list .ticket-item .ticket-info .ticket-name{color:#242424;margin-bottom:12px}.booking-information .book-slots .ticket-list .ticket-item .ticket-info .ticket-price{color:#5e5e5e}.booking-information .book-slots .ticket-list .ticket-item .ticket-quantity{width:50%;display:inline-block}.booking-information .book-slots .ticket-list .ticket-item .ticket-quantity .control-group{margin:0}.booking-information .book-slots .ticket-list .ticket-item p{color:#242424;margin-bottom:0;font-weight:400}.booking-information .book-slots .ticket-total{font-size:16px;font-weight:600;color:#242424;padding-top:16px}.booking-information .book-slots .ticket-total>div{margin-bottom:12px}.booking-information .book-slots .ticket-total>div:last-child{margin-bottom:0}.booking-information .book-slots .ticket-total>div p{color:#242424;font-weight:400;margin-top:4px}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>icon-dropdown</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-dropdown" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="" fill="#242424" transform="translate(4.000000, 4.514496) rotate(-270.000000) translate(-4.000000, -4.514496) " points="1.48080444 8.51449585 4.54690552 4.51449585 1.48080444 0.51449585 3.48550415 0.51449585 6.51919556 4.51449585 3.48550415 8.51449585"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 745 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50 (54983) - http://www.bohemiancoding.com/sketch -->
<title>icon-dropdown-up</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-dropdown-up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="" fill="#242424" transform="translate(4.000000, 3.519196) scale(1, -1) rotate(-270.000000) translate(-4.000000, -3.519196) " points="1.48080444 7.51919556 4.54690552 3.51919556 1.48080444 -0.480804443 3.48550415 -0.480804443 6.51919556 3.51919556 3.48550415 7.51919556"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>tab/heading/icon/address</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="tab/heading/icon/address" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M15.5,23.897235 C20.2087657,20.0716385 22.5,16.8805311 22.5,14.5 C22.5,10.6340068 19.3659932,7.5 15.5,7.5 C11.6340068,7.5 8.5,10.6340068 8.5,14.5 C8.5,16.8805311 10.7912343,20.0716385 15.5,23.897235 Z" id="Oval" stroke="#242424" stroke-width="3"></path>
<circle id="Oval-2" fill="#242424" cx="15.5" cy="14.5" r="2.5"></circle>
</g>
</svg>

After

Width:  |  Height:  |  Size: 835 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>icon-menu copy</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-menu-copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
<g id="Group-3" transform="translate(7.000000, 5.000000)" stroke="#242424" stroke-width="3">
<g id="Group-2">
<g id="Group">
<path d="M0,1 C0,6.2 2.66666667,10.5333333 8,14" id="Path-24"></path>
<path d="M0,0.860920013 L3.33066907e-16,0.860920013 C0.692541156,0.399225909 1.62823371,0.58636442 2.08992781,1.27890558 C2.0981214,1.29119595 2.10613384,1.30360612 2.11396256,1.31613206 L2.36753004,1.72184003 C2.99745806,2.72972486 2.84834566,4.03901444 2.00791685,4.87944325 L1.29138002,5.59598008" id="Path-25" fill="#242424"></path>
<path d="M8.63269516,13.7386314 L5,10.4383311 L5.96373991,10.125193 C7.09411415,9.75791214 8.32839532,10.2194571 8.94044595,11.2382972 L9.18681717,11.648415 C9.19442371,11.6610771 9.20184367,11.6738503 9.20907467,11.6867306 C9.61652862,12.4125102 9.35847472,13.3311774 8.63269516,13.7386314 Z" id="Path-25" stroke-linejoin="round"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>tab/heading/icon/calender</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="tab/heading/icon/calender" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(7.000000, 8.000000)" stroke="#242424" stroke-width="3">
<rect id="Rectangle-10" x="1.5" y="1.5" width="16" height="13"></rect>
</g>
<rect id="Rectangle" fill="#242424" x="7" y="13" width="19" height="3" rx="1.5"></rect>
<rect id="Rectangle-2" fill="#242424" x="11" y="5" width="3" height="6"></rect>
<rect id="Rectangle-2-Copy" fill="#242424" x="19" y="5" width="3" height="6"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1,3 @@
{
"/css/booking-product.css": "/css/booking-product.css?id=083d9215be8e5552a0e7"
}

View File

@ -0,0 +1,10 @@
<?php
return [
'booking' => [
'key' => 'booking',
'name' => 'Booking',
'class' => 'Webkul\BookingProduct\Type\Booking',
'sort' => 7
]
];

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProduct
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProductAppointmentSlot
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProductDefaultSlot
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProductEventSlot
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProductRentalSlot
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Webkul\BookingProduct\Contracts;
interface BookingProductTableSlot
{
}

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_products', function (Blueprint $table) {
$table->increments('id');
$table->string('type');
$table->string('location')->nullable();
$table->boolean('show_location')->default(0);
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_products');
}
}

View File

@ -0,0 +1,39 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductDefaultSlotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_product_default_slots', function (Blueprint $table) {
$table->increments('id');
$table->string('booking_type');
$table->date('available_from')->nullable();
$table->date('available_to')->nullable();
$table->integer('duration')->nullable();
$table->integer('break_time')->nullable();
$table->json('slots')->nullable();
$table->integer('booking_product_id')->unsigned();
$table->foreign('booking_product_id')->references('id')->on('booking_products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_product_default_slots');
}
}

View File

@ -0,0 +1,41 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductAppointmentSlotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_product_appointment_slots', function (Blueprint $table) {
$table->increments('id');
$table->integer('duration')->nullable();
$table->integer('break_time')->nullable();
$table->boolean('available_every_week')->nullable();
$table->date('available_from')->nullable();
$table->date('available_to')->nullable();
$table->boolean('slot_has_quantity')->nullable();
$table->boolean('same_slot_all_days')->nullable();
$table->json('slots')->nullable();
$table->integer('booking_product_id')->unsigned();
$table->foreign('booking_product_id')->references('id')->on('booking_products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_product_appointment_slots');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductEventSlotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_product_event_slots', function (Blueprint $table) {
$table->increments('id');
$table->datetime('available_from')->nullable();
$table->datetime('available_to')->nullable();
$table->json('slots')->nullable();
$table->integer('booking_product_id')->unsigned();
$table->foreign('booking_product_id')->references('id')->on('booking_products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_product_event_slots');
}
}

View File

@ -0,0 +1,42 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductRentalSlotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_product_rental_slots', function (Blueprint $table) {
$table->increments('id');
$table->string('renting_type');
$table->decimal('daily_price', 12, 4)->default(0)->nullable();;
$table->decimal('hourly_price', 12, 4)->default(0)->nullable();;
$table->boolean('available_every_week')->nullable();
$table->date('available_from')->nullable();
$table->date('available_to')->nullable();
$table->boolean('slot_has_quantity')->nullable();
$table->boolean('same_slot_all_days')->nullable();
$table->json('slots')->nullable();
$table->integer('booking_product_id')->unsigned();
$table->foreign('booking_product_id')->references('id')->on('booking_products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_product_rental_slots');
}
}

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateBookingProductTableSlotsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('booking_product_table_slots', function (Blueprint $table) {
$table->increments('id');
$table->string('price_type');
$table->integer('guest_limit')->default(0);
$table->integer('duration');
$table->integer('break_time');
$table->integer('prevent_scheduling_before');
$table->boolean('available_every_week')->nullable();
$table->date('available_from')->nullable();
$table->date('available_to')->nullable();
$table->boolean('same_slot_all_days')->nullable();
$table->json('slots')->nullable();
$table->integer('booking_product_id')->unsigned();
$table->foreign('booking_product_id')->references('id')->on('booking_products')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('booking_product_table_slots');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\BookingProduct\Helpers;
/**
* AppointmentSlot Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class AppointmentSlot extends Booking
{
}

View File

@ -0,0 +1,276 @@
<?php
namespace Webkul\BookingProduct\Helpers;
use Carbon\Carbon;
use Webkul\BookingProduct\Repositories\BookingProductDefaultSlotRepository;
use Webkul\BookingProduct\Repositories\BookingProductAppointmentSlotRepository;
use Webkul\BookingProduct\Repositories\BookingProductEventSlotRepository;
use Webkul\BookingProduct\Repositories\BookingProductRentalSlotRepository;
use Webkul\BookingProduct\Repositories\BookingProductTableSlotRepository;
/**
* Booking Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Booking
{
/**
* @return array
*/
protected $typeRepositories = [];
/**
* @return array
*/
protected $daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
/**
* Create a new helper instance.
*
* @param Webkul\BookingProduct\Repositories\BookingProductDefaultSlotRepository $bookingProductDefaultSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductAppointmentSlotRepository $bookingProductAppointmentSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductEventSlotRepository $bookingProductEventSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductRentalSlotRepository $bookingProductRentalSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductTableSlotRepository $bookingProductTableSlotRepository
* @return void
*/
public function __construct(
BookingProductDefaultSlotRepository $bookingProductDefaultSlotRepository,
BookingProductAppointmentSlotRepository $bookingProductAppointmentSlotRepository,
BookingProductEventSlotRepository $bookingProductEventSlotRepository,
BookingProductRentalSlotRepository $bookingProductRentalSlotRepository,
BookingProductTableSlotRepository $bookingProductTableSlotRepository
)
{
$this->typeRepositories['default'] = $bookingProductDefaultSlotRepository;
$this->typeRepositories['appointment'] = $bookingProductAppointmentSlotRepository;
$this->typeRepositories['event'] = $bookingProductEventSlotRepository;
$this->typeRepositories['rental'] = $bookingProductRentalSlotRepository;
$this->typeRepositories['table'] = $bookingProductTableSlotRepository;
}
/**
* Returns the booking information
*
* @param BookingProduct $bookingProduct
* @return array
*/
public function getWeekSlotDurations($bookingProduct)
{
$slotsByDays = [];
$bookingProductSlot = $this->typeRepositories[$bookingProduct->type]->findOneByField('booking_product_id', $bookingProduct->id);
$availabileDays = $this->getAvailableWeekDays($bookingProductSlot);
foreach ($this->daysOfWeek as $index => $isOpen) {
$slots = [];
if ($isOpen)
$slots = $bookingProductSlot->same_slot_all_days ? $bookingProductSlot->slots : ($bookingProductSlot->slots[$index] ?? []);
$slotsByDays[] = [
'name' => trans($this->daysOfWeek[$index]),
'slots' => isset($availabileDays[$index]) ? $this->conver24To12Hours($slots) : []
];
}
return $slotsByDays;
}
/**
* Returns html of slots for a current day
*
* @param BookingProduct $bookingProduct
* @return string
*/
public function getTodaySlotsHtml($bookingProduct)
{
$slots = [];
foreach ($this->getTodaySlots($bookingProduct) as $slot) {
$slots[] = $slot['from'] . ' - ' . $slot['to'];
}
return count($slots)
? implode(' | ', $slots)
: '<span class="text-danger">' . trans('bookingproduct::app.shop.products.closed') . '</span>';
}
/**
* Returns slots for a current day
*
* @param BookingProduct $bookingProduct
* @return array
*/
public function getTodaySlots($bookingProduct)
{
$weekSlots = $this->getWeekSlotDurations($bookingProduct);
return $weekSlots[Carbon::now()->format('w')]['slots'];
}
/**
* Returns the available week days
*
* @param Object $bookingProductSlot
* @return array
*/
public function getAvailableWeekDays($bookingProductSlot)
{
if ($bookingProductSlot->available_every_week)
return $this->daysOfWeek;
$days = [];
$currentTime = Carbon::now();
$availableFrom = Carbon::createFromTimeString($bookingProductSlot->available_from . " 00:00:01");
$availableTo = Carbon::createFromTimeString($bookingProductSlot->available_to . " 23:59:59");
for ($i = 0; $i < 7; $i++) {
$date = clone $currentTime;
$date->addDays($i);
if ($date >= $availableFrom && $date <= $availableTo)
$days[$i] = $date->format('l');
}
return $this->sortDaysOfWeek($days);
}
/**
* Sort days
*
* @param array $days
* @return array
*/
public function sortDaysOfWeek($days)
{
$daysAux = [];
foreach ($days as $day) {
$key = array_search($day, $this->daysOfWeek);
if ($key !== FALSE) {
$daysAux[$key] = $day;
}
}
ksort($daysAux);
return $daysAux;
}
/**
* Convert time from 24 to 12 hour format
*
* @param array $slots
* @return array
*/
public function conver24To12Hours($slots)
{
if (! $slots)
return [];
foreach ($slots as $index => $slot) {
$slots[$index]['from'] = Carbon::createFromTimeString($slot['from'])->format("h:i a");
$slots[$index]['to'] = Carbon::createFromTimeString($slot['to'])->format("h:i a");
}
return $slots;
}
/**
* Returns slots for a perticular day
*
* @param BookingProduct $bookingProduct
* @param string $date
* @return array
*/
public function getSlotsByDate($bookingProduct, $date)
{
$bookingProductSlot = $this->typeRepositories[$bookingProduct->type]->findOneByField('booking_product_id', $bookingProduct->id);
if (! is_array($bookingProductSlot->slots) || ! count($bookingProductSlot->slots))
return [];
$requestedDate = Carbon::createFromTimeString($date . " 00:00:00");
$currentTime = Carbon::now();
$availableFrom = ! $bookingProductSlot->available_every_week
? Carbon::createFromTimeString($bookingProductSlot->available_from . " 00:00:00")
: Carbon::createFromTimeString($currentTime->format('Y-m-d') . ' 00:00:00');
$availableTo = ! $bookingProductSlot->available_every_week
? Carbon::createFromTimeString($bookingProductSlot->available_to . ' 23:59:59')
: Carbon::createFromTimeString('2080-01-01 00:00:00');
$timeDurations = $bookingProductSlot->same_slot_all_days
? $bookingProductSlot->slots
: ($bookingProductSlot->slots[$requestedDate->format('w')] ?? []);
$slots = [];
foreach ($timeDurations as $timeDuration) {
$fromChunks = explode(':', $timeDuration['from']);
$toChunks = explode(':', $timeDuration['to']);
$startDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$startDayTime->addMinutes(($fromChunks[0] * 60) + $fromChunks[1]);
$tempStartDayTime = clone $startDayTime;
$endDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$endDayTime->addMinutes(($toChunks[0] * 60) + $toChunks[1]);
$isFirstIteration = true;
while (1) {
$from = clone $tempStartDayTime;
$tempStartDayTime->addMinutes($bookingProductSlot->duration);
if ($isFirstIteration) {
$isFirstIteration = false;
} else {
$from->modify('+' . $bookingProductSlot->break_time . ' minutes');
$tempStartDayTime->modify('+' . $bookingProductSlot->break_time . ' minutes');
}
$to = clone $tempStartDayTime;
if (($startDayTime <= $from && $from <= $availableTo)
&& ($availableTo >= $to && $to >= $startDayTime)
&& ($startDayTime <= $from && $from <= $endDayTime)
&& ($endDayTime >= $to && $to >= $startDayTime)) {
// Get already ordered qty for this slot
$orderedQty = 0;
$qty = isset($timeDuration['qty']) ? ( $timeDuration['qty'] - $orderedQty ) : 1;
if ($qty && $currentTime <= $from) {
$slots[] = [
'from' => $from->format('h:i A'),
'to' => $to->format('h:i A'),
'timestamp' => $from->getTimestamp() . '-' . $to->getTimestamp(),
'qty' => $qty,
];
}
} else {
break;
}
}
}
return $slots;
}
}

View File

@ -0,0 +1,154 @@
<?php
namespace Webkul\BookingProduct\Helpers;
use Carbon\Carbon;
/**
* DefaultSlot Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class DefaultSlot extends Booking
{
/**
* @return array
*/
protected $daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
/**
* Returns slots for a perticular day
*
* @param BookingProduct $bookingProduct
* @param string $date
* @return array
*/
public function getSlotsByDate($bookingProduct, $date)
{
$bookingProductSlot = $this->typeRepositories[$bookingProduct->type]->findOneByField('booking_product_id', $bookingProduct->id);
if (! is_array($bookingProductSlot->slots) || ! count($bookingProductSlot->slots))
return [];
$requestedDate = Carbon::createFromTimeString($date . " 00:00:00");
$currentTime = Carbon::now();
$availableFrom = Carbon::createFromTimeString($bookingProductSlot->available_from . " 00:00:00");
$availableTo = Carbon::createFromTimeString($bookingProductSlot->available_to . ' 23:59:59');
if ($requestedDate < $currentTime
|| $requestedDate < $availableFrom
|| $requestedDate > $availableTo)
return [];
$slots = [];
return $bookingProductSlot->booking_type == 'one'
? $this->getOneBookingForManyDaysSlots($bookingProductSlot, $requestedDate)
: $this->getManyBookingsforOneDaySlots($bookingProductSlot, $requestedDate);
}
/**
* Returns slots for One Booking For Many Days
*
* @param BookingProductSlot $bookingProductSlot
* @param string $requestedDate
* @return array
*/
public function getOneBookingForManyDaysSlots($bookingProductSlot, $requestedDate)
{
$slots = [];
foreach ($bookingProductSlot->slots as $timeDuration) {
if ($requestedDate->dayOfWeek != $timeDuration['from_day'])
continue;
$startDate = clone $requestedDate->modify('this ' . $this->daysOfWeek[$timeDuration['from_day']]);
$endDate = clone $requestedDate->modify('this ' . $this->daysOfWeek[$timeDuration['to_day']]);
$slots[] = [
'from' => $startDate->format('d F, Y h:i A'),
'to' => $endDate->format('d F, Y h:i A'),
'timestamp' => $startDate->getTimestamp() . '-' . $endDate->getTimestamp(),
];
}
return $slots;
}
/**
* Returns slots for Many Bookings for One Day
*
* @param BookingProductSlot $bookingProductSlot
* @param string $requestedDate
* @return array
*/
public function getManyBookingsforOneDaySlots($bookingProductSlot, $requestedDate)
{
$currentTime = Carbon::now();
$availableFrom = Carbon::createFromTimeString($bookingProductSlot->available_from . ' 00:00:00');
$availableTo = Carbon::createFromTimeString($bookingProductSlot->available_to . ' 23:59:59');
$timeDuration = $bookingProductSlot->slots[$requestedDate->format('w')] ?? [];
if (! count($timeDuration) || ! $timeDuration['status'])
return [];
$slots = [];
$fromChunks = explode(':', $timeDuration['from']);
$toChunks = explode(':', $timeDuration['to']);
$startDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$startDayTime->addMinutes(($fromChunks[0] * 60) + $fromChunks[1]);
$tempStartDayTime = clone $startDayTime;
$endDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$endDayTime->addMinutes(($toChunks[0] * 60) + $toChunks[1]);
$isFirstIteration = true;
while (1) {
$from = clone $tempStartDayTime;
$tempStartDayTime->addMinutes($bookingProductSlot->duration);
if ($isFirstIteration) {
$isFirstIteration = false;
} else {
$from->modify('+' . $bookingProductSlot->break_time . ' minutes');
$tempStartDayTime->modify('+' . $bookingProductSlot->break_time . ' minutes');
}
$to = clone $tempStartDayTime;
if (($startDayTime <= $from && $from <= $availableTo)
&& ($availableTo >= $to && $to >= $startDayTime)
&& ($startDayTime <= $from && $from <= $endDayTime)
&& ($endDayTime >= $to && $to >= $startDayTime)) {
// Get already ordered qty for this slot
$orderedQty = 0;
$qty = isset($timeDuration['qty']) ? ( $timeDuration['qty'] - $orderedQty ) : 1;
if ($qty && $currentTime <= $from) {
$slots[] = [
'from' => $from->format('h:i A'),
'to' => $to->format('h:i A'),
'timestamp' => $from->getTimestamp() . '-' . $to->getTimestamp(),
'qty' => $qty,
];
}
} else {
break;
}
}
return $slots;
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Webkul\BookingProduct\Helpers;
use Carbon\Carbon;
/**
* EventSlot Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class EventSlot extends Booking
{
/**
* Returns event date
*
* @param BookingProduct $bookingProduct
* @return string
*/
public function getEventDate($bookingProduct)
{
$from = Carbon::createFromTimeString($bookingProduct->event_slot->available_from)->format('d F, Y h:i A');
$to = Carbon::createFromTimeString($bookingProduct->event_slot->available_to)->format('d F, Y h:i A');
return $from . ' - ' . $to;
}
/**
* Returns tickets
*
* @param BookingProduct $bookingProduct
* @return array
*/
public function getTickets($bookingProduct)
{
if (! $bookingProduct->event_slot)
return;
return $this->formatPrice($bookingProduct->event_slot->slots);
}
/**
* Format ticket price
*
* @param array $tickets
* @return array
*/
public function formatPrice($tickets)
{
foreach ($tickets as $index => $ticket) {
$tickets[$index]['converted_price'] = core()->convertPrice($ticket['price']);
$tickets[$index]['formated_price'] = $formatedPrice = core()->currency($ticket['price']);
$tickets[$index]['formated_price_text'] = trans('bookingproduct::app.shop.products.per-ticket-price', ['price' => $formatedPrice]);
}
return $tickets;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace Webkul\BookingProduct\Helpers;
use Carbon\Carbon;
/**
* RentalSlot Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class RentalSlot extends Booking
{
/**
* Returns slots for a perticular day
*
* @param BookingProduct $bookingProduct
* @param string $date
* @return array
*/
public function getSlotsByDate($bookingProduct, $date)
{
$bookingProductSlot = $this->typeRepositories[$bookingProduct->type]->findOneByField('booking_product_id', $bookingProduct->id);
if (! is_array($bookingProductSlot->slots) || ! count($bookingProductSlot->slots))
return [];
$requestedDate = Carbon::createFromTimeString($date . " 00:00:00");
$currentTime = Carbon::now();
$availableFrom = ! $bookingProductSlot->available_every_week
? Carbon::createFromTimeString($bookingProductSlot->available_from . ' 00:00:00')
: Carbon::createFromTimeString($currentTime->format('Y-m-d') . ' 00:00:00');
$availableTo = ! $bookingProductSlot->available_every_week
? Carbon::createFromTimeString($bookingProductSlot->available_to . ' 23:59:59')
: Carbon::createFromTimeString('2080-01-01 00:00:00');
$timeDurations = $bookingProductSlot->same_slot_all_days
? $bookingProductSlot->slots
: $bookingProductSlot->slots[$requestedDate->format('w')];
$slots = [];
foreach ($timeDurations as $index => $timeDuration) {
$fromChunks = explode(':', $timeDuration['from']);
$toChunks = explode(':', $timeDuration['to']);
$startDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$startDayTime->addMinutes(($fromChunks[0] * 60) + $fromChunks[1]);
$tempStartDayTime = clone $startDayTime;
$endDayTime = Carbon::createFromTimeString($requestedDate->format('Y-m-d') . ' 00:00:00');
$endDayTime->addMinutes(($toChunks[0] * 60) + $toChunks[1]);
while (1) {
$from = clone $tempStartDayTime;
$tempStartDayTime->addMinutes(60);
$to = clone $tempStartDayTime;
if (($startDayTime <= $from && $from <= $availableTo)
&& ($availableTo >= $to && $to >= $startDayTime)
&& ($startDayTime <= $from && $from <= $endDayTime)
&& ($endDayTime >= $to && $to >= $startDayTime)) {
// Get already ordered qty for this slot
$orderedQty = 0;
$qty = isset($timeDuration['qty']) ? ( $timeDuration['qty'] - $orderedQty ) : 1;
if ($qty && $currentTime <= $from) {
if (! isset($slots[$index]))
$slots[$index]['time'] = $startDayTime->format('h:i A') . ' - ' . $endDayTime->format('h:i A');
$slots[$index]['slots'][] = [
'from' => $from->format('h:i A'),
'to' => $to->format('h:i A'),
'from_timestamp' => $from->getTimestamp(),
'to_timestamp' => $to->getTimestamp(),
'qty' => $qty
];
}
} else {
break;
}
}
}
return $slots;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace Webkul\BookingProduct\Helpers;
/**
* TableSlot Helper
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class TableSlot extends Booking
{
}

View File

@ -0,0 +1,78 @@
<?php
namespace Webkul\BookingProduct\Http\Controllers\Shop;
use Webkul\BookingProduct\Repositories\BookingProductRepository;
use Webkul\BookingProduct\Helpers\DefaultSlot as DefaultSlotHelper;
use Webkul\BookingProduct\Helpers\AppointmentSlot as AppointmentSlotHelper;
use Webkul\BookingProduct\Helpers\RentalSlot as RentalSlotHelper;
use Webkul\BookingProduct\Helpers\EventSlot as EventSlotHelper;
use Webkul\BookingProduct\Helpers\TableSlot as TableSlotHelper;
/**
* BookingProduct page controller
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductController extends Controller
{
/**
* Booking object
*
* @var Object
*/
protected $bookingHelper;
/**
* @return array
*/
protected $bookingHelpers = [];
/**
* Create a new helper instance.
*
* @param Webkul\BookingProduct\Repositories\BookingProductRepository $bookingProductRepository
* @param Webkul\BookingProduct\Helpers\DefaultSlot $defaultSlotHelper
* @param Webkul\BookingProduct\Helpers\AppointmentSlot $appointmentSlotHelper
* @param Webkul\BookingProduct\Helpers\RentalSlot $rentalSlotHelper
* @param Webkul\BookingProduct\Helpers\EventSlot $eventSlotHelper
* @param Webkul\BookingProduct\Helpers\TableSlot $tableSlotHelper
* @return void
*/
public function __construct(
BookingProductRepository $bookingProductRepository,
DefaultSlotHelper $defaultSlotHelper,
AppointmentSlotHelper $appointmentSlotHelper,
RentalSlotHelper $rentalSlotHelper,
EventSlotHelper $eventSlotHelper,
TableSlotHelper $tableSlotHelper
)
{
$this->bookingProductRepository = $bookingProductRepository;
$this->bookingHelpers['default'] = $defaultSlotHelper;
$this->bookingHelpers['appointment'] = $appointmentSlotHelper;
$this->bookingHelpers['rental'] = $rentalSlotHelper;
$this->bookingHelpers['event'] = $eventSlotHelper;
$this->bookingHelpers['table'] = $tableSlotHelper;
}
/**
* Display the specified resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$bookingProduct = $this->bookingProductRepository->find(request('id'));
return response()->json([
'data' => $this->bookingHelpers[$bookingProduct->type]->getSlotsByDate($bookingProduct, request()->get('date'))
]);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Webkul\BookingProduct\Http\Controllers\Shop;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
}

View File

@ -0,0 +1,5 @@
<?php
Route::group(['middleware' => ['web', 'theme', 'locale', 'currency']], function () {
Route::get('/booking-slots/{id}', 'Webkul\BookingProduct\Http\Controllers\Shop\BookingProductController@index')->name('booking_product.slots.index');
});

View File

@ -0,0 +1,53 @@
<?php
namespace Webkul\BookingProduct\Listeners;
use Webkul\BookingProduct\Repositories\BookingProductRepository;
/**
* Product Event handler
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Product
{
/**
* BookingProductRepository Object
*
* @var Object
*/
protected $bookingProductRepository;
/**
* Create a new listener instance.
*
* @param Webkul\BookingProduct\Repositories\BookingProductRepository $bookingProductRepository
* @return void
*/
public function __construct(BookingProductRepository $bookingProductRepository)
{
$this->bookingProductRepository = $bookingProductRepository;
}
/**
* After the product is updated
*
* @return void
*/
public function afterProductUpdated($product)
{
if ($product->type != 'booking' || ! request('booking'))
return;
$bookingProduct = $this->bookingProductRepository->findOneByField('product_id', $product->id);
if ($bookingProduct) {
$this->bookingProductRepository->update(request('booking'), $bookingProduct->id);
} else {
$this->bookingProductRepository->create(array_merge(request('booking'), [
'product_id' => $product->id
]));
}
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\Product\Models\ProductProxy;
use Webkul\BookingProduct\Contracts\BookingProduct as BookingProductContract;
class BookingProduct extends Model implements BookingProductContract
{
protected $fillable = ['location', 'show_location', 'type', 'product_id'];
protected $with = ['default_slot', 'appointment_slot', 'event_slot', 'rental_slot', 'table_slot'];
/**
* The Product Default Booking that belong to the product booking.
*/
public function default_slot()
{
return $this->hasOne(BookingProductDefaultSlotProxy::modelClass());
}
/**
* The Product Appointment Booking that belong to the product booking.
*/
public function appointment_slot()
{
return $this->hasOne(BookingProductAppointmentSlotProxy::modelClass());
}
/**
* The Product Event Booking that belong to the product booking.
*/
public function event_slot()
{
return $this->hasOne(BookingProductEventSlotProxy::modelClass());
}
/**
* The Product Rental Booking that belong to the product booking.
*/
public function rental_slot()
{
return $this->hasOne(BookingProductRentalSlotProxy::modelClass());
}
/**
* The Product Table Booking that belong to the product booking.
*/
public function table_slot()
{
return $this->hasOne(BookingProductTableSlotProxy::modelClass());
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\BookingProduct\Contracts\BookingProductAppointmentSlot as BookingProductAppointmentSlotContract;
class BookingProductAppointmentSlot extends Model implements BookingProductAppointmentSlotContract
{
public $timestamps = false;
protected $casts = ['slots' => 'array'];
protected $fillable = ['duration', 'break_time', 'available_every_week', 'slot_has_quantity', 'same_slot_all_days', 'slots', 'booking_product_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductAppointmentSlotProxy extends ModelProxy
{
}

View File

@ -0,0 +1,25 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\BookingProduct\Contracts\BookingProductDefaultSlot as BookingProductDefaultSlotContract;
class BookingProductDefaultSlot extends Model implements BookingProductDefaultSlotContract
{
protected $table = 'booking_product_default_slots';
public $timestamps = false;
protected $casts = ['slots' => 'array'];
protected $fillable = ['booking_type', 'duration', 'break_time', 'available_from', 'available_to', 'slots', 'booking_product_id'];
/**
* Get the product that owns the attribute value.
*/
public function booking_product()
{
return $this->belongsTo(BookingProductProxy::modelClass());
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductDefaultSlotProxy extends ModelProxy
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\BookingProduct\Contracts\BookingProductEventSlot as BookingProductEventSlotContract;
class BookingProductEventSlot extends Model implements BookingProductEventSlotContract
{
public $timestamps = false;
protected $casts = ['slots' => 'array'];
protected $fillable = ['available_from', 'available_to', 'slots', 'booking_product_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductEventSlotProxy extends ModelProxy
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductProxy extends ModelProxy
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\BookingProduct\Contracts\BookingProductRentalSlot as BookingProductRentalSlotContract;
class BookingProductRentalSlot extends Model implements BookingProductRentalSlotContract
{
public $timestamps = false;
protected $casts = ['slots' => 'array'];
protected $fillable = ['renting_type', 'daily_price', 'hourly_price', 'available_every_week', 'slot_has_quantity', 'same_slot_all_days', 'slots', 'booking_product_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductRentalSlotProxy extends ModelProxy
{
}

View File

@ -0,0 +1,15 @@
<?php
namespace Webkul\BookingProduct\Models;
use Illuminate\Database\Eloquent\Model;
use Webkul\BookingProduct\Contracts\BookingProductTableSlot as BookingProductTableSlotContract;
class BookingProductTableSlot extends Model implements BookingProductTableSlotContract
{
public $timestamps = false;
protected $casts = ['slots' => 'array'];
protected $fillable = ['price_type', 'guest_limit', 'duration', 'break_time', 'prevent_scheduling_before', 'available_every_week', 'available_from', 'available_to', 'same_slot_all_days', 'slots', 'booking_product_id'];
}

View File

@ -0,0 +1,10 @@
<?php
namespace Webkul\BookingProduct\Models;
use Konekt\Concord\Proxies\ModelProxy;
class BookingProductTableSlotProxy extends ModelProxy
{
}

View File

@ -0,0 +1,42 @@
<?php
namespace Webkul\BookingProduct\Providers;
use Illuminate\Support\ServiceProvider;
class BookingProductServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
$this->loadRoutesFrom(__DIR__ . '/../Http/front-routes.php');
$this->loadMigrationsFrom(__DIR__ . '/../Database/Migrations');
$this->loadTranslationsFrom(__DIR__ . '/../Resources/lang', 'bookingproduct');
$this->loadViewsFrom(__DIR__ . '/../Resources/views', 'bookingproduct');
$this->publishes([
__DIR__ . '/../../publishable/assets' => public_path('themes/default/assets'),
], 'public');
$this->app->register(EventServiceProvider::class);
}
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
dirname(__DIR__) . '/Config/product_types.php', 'product_types'
);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Webkul\BookingProduct\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
Event::listen('bagisto.shop.products.view.short_description.after', function($viewRenderEventManager) {
$viewRenderEventManager->addTemplate('bookingproduct::shop.products.view.booking');
});
Event::listen('catalog.product.update.after', 'Webkul\BookingProduct\Listeners\Product@afterProductUpdated');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Webkul\BookingProduct\Providers;
use Konekt\Concord\BaseModuleServiceProvider;
class ModuleServiceProvider extends BaseModuleServiceProvider
{
protected $models = [
\Webkul\BookingProduct\Models\BookingProduct::class,
\Webkul\BookingProduct\Models\BookingProductDefaultSlot::class,
\Webkul\BookingProduct\Models\BookingProductAppointmentSlot::class,
\Webkul\BookingProduct\Models\BookingProductEventSlot::class,
\Webkul\BookingProduct\Models\BookingProductRentalSlot::class,
\Webkul\BookingProduct\Models\BookingProductTableSlot::class,
];
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProductAppointmentSlot Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductAppointmentSlotRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProductAppointmentSlot';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProductDefaultSlot Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductDefaultSlotRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProductDefaultSlot';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProductEventSlot Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductEventSlotRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProductEventSlot';
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProductRentalSlot Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductRentalSlotRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProductRentalSlot';
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Illuminate\Container\Container as App;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProduct Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductRepository extends Repository
{
/**
* @return array
*/
protected $typeRepositories = [];
/**
* Create a new repository instance.
*
* @param Webkul\BookingProduct\Repositories\BookingProductDefaultSlotRepository $bookingProductDefaultSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductAppointmentSlotRepository $bookingProductAppointmentSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductEventSlotRepository $bookingProductEventSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductRentalSlotRepository $bookingProductRentalSlotRepository
* @param Webkul\BookingProduct\Repositories\BookingProductTableSlotRepository $bookingProductTableSlotRepository
* @return void
*/
public function __construct(
BookingProductDefaultSlotRepository $bookingProductDefaultSlotRepository,
BookingProductAppointmentSlotRepository $bookingProductAppointmentSlotRepository,
BookingProductEventSlotRepository $bookingProductEventSlotRepository,
BookingProductRentalSlotRepository $bookingProductRentalSlotRepository,
BookingProductTableSlotRepository $bookingProductTableSlotRepository,
App $app
)
{
parent::__construct($app);
$this->typeRepositories['default'] = $bookingProductDefaultSlotRepository;
$this->typeRepositories['appointment'] = $bookingProductAppointmentSlotRepository;
$this->typeRepositories['event'] = $bookingProductEventSlotRepository;
$this->typeRepositories['rental'] = $bookingProductRentalSlotRepository;
$this->typeRepositories['table'] = $bookingProductTableSlotRepository;
}
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProduct';
}
/**
* @param array $data
* @return mixed
*/
public function create(array $data)
{
$bookingProduct = parent::create($data);
$this->typeRepositories[$data['type']]->create(array_merge($data, ['booking_product_id' => $bookingProduct->id]));
return $bookingProduct;
}
/**
* @param array $data
* @param integer $id
* @param string $attribute
* @return mixed
*/
public function update(array $data, $id, $attribute = "id")
{
parent::update($data, $id, $attribute);
foreach ($this->typeRepositories as $type => $repository) {
if ($type == $data['type'])
continue;
$repository->deleteWhere(['booking_product_id' => $id]);
}
$bookingProductTypeSlot = $this->typeRepositories[$data['type']]->findOneByField('booking_product_id', $id);
if (isset($data['slots']))
$data['slots'] = $this->formatSlots($data);
if (! $bookingProductTypeSlot) {
$this->typeRepositories[$data['type']]->create(array_merge($data, ['booking_product_id' => $id]));
} else {
$this->typeRepositories[$data['type']]->update($data, $bookingProductTypeSlot->id);
}
}
/**
* @param array $data
* @return array
*/
public function formatSlots($data)
{
if (isset($data['slots']) && isset($data['same_slot_all_days']) && ! $data['same_slot_all_days']) {
for ($i = 0; $i < 7; $i++) {
if (! isset($data['slots'][$i])) {
$data['slots'][$i] = [];
} else {
$count = 0;
$slots = [];
foreach ($data['slots'][$i] as $slot) {
$slots[] = array_merge($slot, ['id' => $i . '_slot_' . $count]);
$count++;
}
$data['slots'][$i] = $slots;
}
}
ksort($data['slots']);
}
return $data['slots'];
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Webkul\BookingProduct\Repositories;
use Webkul\Core\Eloquent\Repository;
/**
* BookingProductTableSlot Repository
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class BookingProductTableSlotRepository extends Repository
{
/**
* Specify Model class name
*
* @return mixed
*/
function model()
{
return 'Webkul\BookingProduct\Contracts\BookingProductTableSlot';
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>icon-dropdown</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-dropdown" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="" fill="#242424" transform="translate(4.000000, 4.514496) rotate(-270.000000) translate(-4.000000, -4.514496) " points="1.48080444 8.51449585 4.54690552 4.51449585 1.48080444 0.51449585 3.48550415 0.51449585 6.51919556 4.51449585 3.48550415 8.51449585"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 745 B

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50 (54983) - http://www.bohemiancoding.com/sketch -->
<title>icon-dropdown-up</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-dropdown-up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="" fill="#242424" transform="translate(4.000000, 3.519196) scale(1, -1) rotate(-270.000000) translate(-4.000000, -3.519196) " points="1.48080444 7.51919556 4.54690552 3.51919556 1.48080444 -0.480804443 3.48550415 -0.480804443 6.51919556 3.51919556 3.48550415 7.51919556"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>tab/heading/icon/address</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="tab/heading/icon/address" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M15.5,23.897235 C20.2087657,20.0716385 22.5,16.8805311 22.5,14.5 C22.5,10.6340068 19.3659932,7.5 15.5,7.5 C11.6340068,7.5 8.5,10.6340068 8.5,14.5 C8.5,16.8805311 10.7912343,20.0716385 15.5,23.897235 Z" id="Oval" stroke="#242424" stroke-width="3"></path>
<circle id="Oval-2" fill="#242424" cx="15.5" cy="14.5" r="2.5"></circle>
</g>
</svg>

After

Width:  |  Height:  |  Size: 835 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>icon-menu copy</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="icon-menu-copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
<g id="Group-3" transform="translate(7.000000, 5.000000)" stroke="#242424" stroke-width="3">
<g id="Group-2">
<g id="Group">
<path d="M0,1 C0,6.2 2.66666667,10.5333333 8,14" id="Path-24"></path>
<path d="M0,0.860920013 L3.33066907e-16,0.860920013 C0.692541156,0.399225909 1.62823371,0.58636442 2.08992781,1.27890558 C2.0981214,1.29119595 2.10613384,1.30360612 2.11396256,1.31613206 L2.36753004,1.72184003 C2.99745806,2.72972486 2.84834566,4.03901444 2.00791685,4.87944325 L1.29138002,5.59598008" id="Path-25" fill="#242424"></path>
<path d="M8.63269516,13.7386314 L5,10.4383311 L5.96373991,10.125193 C7.09411415,9.75791214 8.32839532,10.2194571 8.94044595,11.2382972 L9.18681717,11.648415 C9.19442371,11.6610771 9.20184367,11.6738503 9.20907467,11.6867306 C9.61652862,12.4125102 9.35847472,13.3311774 8.63269516,13.7386314 Z" id="Path-25" stroke-linejoin="round"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
<title>tab/heading/icon/calender</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="tab/heading/icon/calender" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(7.000000, 8.000000)" stroke="#242424" stroke-width="3">
<rect id="Rectangle-10" x="1.5" y="1.5" width="16" height="13"></rect>
</g>
<rect id="Rectangle" fill="#242424" x="7" y="13" width="19" height="3" rx="1.5"></rect>
<rect id="Rectangle-2" fill="#242424" x="11" y="5" width="3" height="6"></rect>
<rect id="Rectangle-2-Copy" fill="#242424" x="19" y="5" width="3" height="6"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1,213 @@
@import "icons";
.booking-information {
margin-bottom: 15px;
border-top: 1px solid #E8E8E8;
padding-top: 15px;
.booking-info-row {
padding-left: 32px;
margin-bottom: 20px;
position: relative;
.icon {
position: absolute;
left: 0;
top: -4px;
}
.title {
color: #5E5E5E;
display: block;
margin-bottom: 5px;
}
.value {
display: block;
margin-bottom: 5px;
.text-danger {
color: #ff5656;
}
}
.toggle {
color: #0041FF;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
.icon {
position: relative;
width: 10px;
height: 10px;
margin-left: 5px;
top: 0;
&.arrow-down-icon {
background-image: url(../images/arrow-down.svg) !important;
}
&.arrow-up-icon {
background-image: url(../images/arrow-up.svg) !important;
}
}
}
.days-availability {
table {
margin-top: 10px;
border-collapse: collapse;
tr {
td {
padding: 5px;
vertical-align: top;
&:first-child {
padding-left: 0;
}
&:last-child {
font-size: 14px;
padding-left: 15px;
padding-right: 0;
color: #5E5E5E;
}
.text-danger {
color: #ff5656;
}
}
}
}
}
}
.book-slots {
padding-top: 25px;
display: inline-block;
width: 100%;
border-top: solid 1px #e8e8e8;
h3 {
font-weight: 600;
font-size: 16px;
color: #242424;
margin-top: 0;
}
label {
color: #3a3a3a;
}
.control-group {
label {
font-size: 16px;
}
.radio {
display: inline-block;
}
}
.control-group-container {
width: 100%;
float: left;
.control-group:not(.quantity) {
width: 50%;
float: left;
.control {
width: 100%;
}
&.date {
padding-right: 5px;
&::after {
position: absolute;
top: 14px;
right: 10px;
}
}
&.slots {
&:first-child {
padding-right: 5px;
}
&:last-child {
padding-left: 5px;
}
}
}
}
.ticket-list {
.ticket-item {
width: 100%;
display: inline-block;
padding: 16px 0;
border-bottom: solid 1px #e8e8e8;
.ticket-info {
width: 50%;
float: left;
.ticket-name {
color: #242424;
margin-bottom: 12px;
}
.ticket-price {
color: #5E5E5E;
}
}
.ticket-quantity {
width: 50%;
display: inline-block;
.control-group {
margin: 0;
}
}
p {
color: #242424;
margin-bottom: 0;
font-weight: 400;
}
}
}
.ticket-total {
font-size: 16px;
font-weight: 600;
color: #242424;
padding-top: 16px;
// border-top: solid 1px #e8e8e8;
> div {
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
p {
color: #242424;
font-weight: 400;
margin-top: 4px;
}
}
}
}
}

View File

@ -0,0 +1,17 @@
.bp-location-icon {
background-image: url("../images/location.svg");
width: 32px;
height: 32px;
}
.bp-slot-icon {
background-image: url("../images/slot.svg");
width: 32px;
height: 32px;
}
.bp-phone-icon {
background-image: url("../images/phone.svg");
width: 32px;
height: 32px;
}

View File

@ -0,0 +1,109 @@
<?php
return [
'admin' => [
'catalog' => [
'products' => [
'booking' => 'Booking Information',
'booking-type' => 'Booking Type',
'default' => 'Default',
'appointment-booking' => 'Appointment Booking',
'event-booking' => 'Event Booking',
'rental-booking' => 'Rental Booking',
'table-booking' => 'Table Booking',
'slot-duration' => 'Slot Duration (Mins)',
'break-time' => 'Break Time b/w Slots (Mins)',
'available-every-week' => 'Available Every Week',
'yes' => 'Yes',
'no' => 'No',
'available-from' => 'Available From',
'available-to' => 'Available To',
'same-slot-all-days' => 'Same Slot All Days',
'slot-has-quantity' => 'Slot has Quantity',
'slots' => 'Slots',
'from' => 'From',
'to' => 'To',
'qty' => 'Qty',
'add-slot' => 'Add Slot',
'sunday' => 'Sunday',
'monday' => 'Monday',
'tuesday' => 'Tuesday',
'wednesday' => 'Wednesday',
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
'renting-type' => 'Renting Type',
'daily' => 'Daily Basis',
'hourly' => 'Hourly Basis',
'daily-hourly' => 'Both (Daily and Hourly Basis)',
'daily-price' => 'Daily Price',
'hourly-price' => 'Hourly Price',
'location' => 'Location',
'show-location' => 'Show Location',
'event-start-date' => 'Event Start Date',
'event-end-date' => 'Event End Date',
'tickets' => 'Tickets',
'add-ticket' => 'Add Ticket',
'name' => 'Name',
'price' => 'Price',
'quantity' => 'Quantity',
'description' => 'Description',
'charged-per' => 'Charged Per',
'guest' => 'Guest',
'table' => 'Table',
'prevent-scheduling-before' => 'Prevent Scheduling Before',
'guest-limit' => 'Guest Limit',
'type' => 'Type',
'many-bookings-for-one-day' => 'Many Bookings for One Day',
'one-booking-for-many-days' => 'One Booking for Many Days',
'day' => 'Day',
'status' => 'Status',
'open' => 'Open',
'close' => 'Close'
]
]
],
'shop' => [
'products' => [
'location' => 'Location',
'contact' => 'Contact',
'email' => 'Email',
'slot-duration' => 'Slot Duration',
'slot-duration-in-minutes' => ':minutes Minutes',
'today-availability' => 'Today Availability',
'sunday' => 'Sunday',
'monday' => 'Monday',
'tuesday' => 'Tuesday',
'wednesday' => 'Wednesday',
'thursday' => 'Thursday',
'friday' => 'Friday',
'saturday' => 'Saturday',
'closed' => 'Closed',
'book-an-appointment' => 'Book an Appointment',
'date' => 'Date',
'slot' => 'Slot',
'rent-an-item' => 'Rent an Item',
'choose-rent-option' => 'Choose Rent Option',
'daily-basis' => 'Daily Basis',
'hourly-basis' => 'Hourly Basis',
'select-time-slot'=> 'Select time slot',
'select-slot' => 'Select Slot',
'select-date' => 'Select date',
'select-rent-time' => 'Select Rent Time',
'from' => 'From',
'to' => 'To',
'book-a-table' => 'Book a Table',
'number-of-tables' => 'Number of Tables',
'special-notes' => 'Special Request/Notes',
'event-on' => 'Event On',
'book-your-ticket' => 'Book Your Ticket',
'per-ticket-price' => ':price Per Ticket',
'number-of-tickets' => 'Number of Tickets',
'total-tickets' => 'Total Tickets',
'base-price' => 'Base Price',
'total-price' => 'Total Price',
'base-price-info' => '(This will be apply to each type of ticket for each quantity)'
]
]
];

View File

@ -0,0 +1,84 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.before', ['product' => $product]) !!}
<accordian :title="'{{ __('bookingproduct::app.admin.catalog.products.booking') }}'" :active="true">
<div slot="body">
<booking-information></booking-information>
</div>
</accordian>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.after', ['product' => $product]) !!}
@push('scripts')
<?php $bookingProduct = app('\Webkul\BookingProduct\Repositories\BookingProductRepository')->findOneByField('product_id', $product->id) ?>
@parent
<script type="text/x-template" id="booking-information-template">
<div>
<div class="control-group" :class="[errors.has('booking[type]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.booking-type') }}</label>
<select v-validate="'required'" name="booking[type]" v-model="booking_type" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.booking-type') }}&quot;">
<option value="default">{{ __('bookingproduct::app.admin.catalog.products.default') }}</option>
<option value="appointment">{{ __('bookingproduct::app.admin.catalog.products.appointment-booking') }}</option>
<option value="event">{{ __('bookingproduct::app.admin.catalog.products.event-booking') }}</option>
<option value="rental">{{ __('bookingproduct::app.admin.catalog.products.rental-booking') }}</option>
<option value="table">{{ __('bookingproduct::app.admin.catalog.products.table-booking') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[type]')">@{{ errors.first('booking[type]') }}</span>
</div>
<div class="control-group">
<label>{{ __('bookingproduct::app.admin.catalog.products.location') }}</label>
<input type="text" name="booking[location]" v-model="location" class="control"/>
</div>
<div class="default-booking-section" v-if="booking_type == 'default'">
@include ('bookingproduct::admin.catalog.products.accordians.booking.default', ['bookingProduct' => $bookingProduct])
</div>
<div class="appointment-booking-section" v-if="booking_type == 'appointment'">
@include ('bookingproduct::admin.catalog.products.accordians.booking.appointment', ['bookingProduct' => $bookingProduct])
</div>
<div class="event-booking-section" v-if="booking_type == 'event'">
@include ('bookingproduct::admin.catalog.products.accordians.booking.event', ['bookingProduct' => $bookingProduct])
</div>
<div class="rental-booking-section" v-if="booking_type == 'rental'">
@include ('bookingproduct::admin.catalog.products.accordians.booking.rental', ['bookingProduct' => $bookingProduct])
</div>
<div class="table-booking-section" v-if="booking_type == 'table'">
@include ('bookingproduct::admin.catalog.products.accordians.booking.table', ['bookingProduct' => $bookingProduct])
</div>
</div>
</script>
<script>
var bookingProduct = @json($bookingProduct);
Vue.component('booking-information', {
template: '#booking-information-template',
inject: ['$validator'],
data: function() {
return {
booking_type: bookingProduct ? bookingProduct.type : 'default',
location: ''
}
}
});
</script>
@include ('bookingproduct::admin.catalog.products.accordians.booking.slots')
@endpush

View File

@ -0,0 +1,131 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.appointment.before', ['product' => $product]) !!}
<appointment-booking></appointment-booking>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.appointment.after', ['product' => $product]) !!}
@push('scripts')
@parent
<script type="text/x-template" id="appointment-booking-template">
<div>
<div class="control-group" :class="[errors.has('booking[duration]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[duration]" v-model="appointment_booking.duration" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[duration]')">@{{ errors.first('booking[duration]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[break_time]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.break-time') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[break_time]" v-model="appointment_booking.break_time" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.break-time') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[break_time]')">@{{ errors.first('booking[break_time]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_every_week]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}</label>
<select v-validate="'required'" name="booking[available_every_week]" v-model="appointment_booking.available_every_week" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[available_every_week]')">@{{ errors.first('booking[available_every_week]') }}</span>
</div>
<div v-if="! parseInt(appointment_booking.available_every_week)">
<div class="control-group" :class="[errors.has('booking[available_from]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-from') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_from]" v-model="appointment_booking.available_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-from') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_from]')">@{{ errors.first('booking[available_from]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_to]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-to') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_to]" v-model="appointment_booking.available_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-to') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_to]')">@{{ errors.first('booking[available_to]') }}</span>
</div>
</div>
<div class="control-group" :class="[errors.has('booking[slot_has_quantity]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.slot-has-quantity') }}</label>
<select v-validate="'required'" name="booking[slot_has_quantity]" v-model="appointment_booking.slot_has_quantity" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.slot-has-quantity') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot_has_quantity]')">@{{ errors.first('booking[slot_has_quantity]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[same_slot_all_days]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}</label>
<select v-validate="'required'" name="booking[same_slot_all_days]" v-model="appointment_booking.same_slot_all_days" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[same_slot_all_days]')">@{{ errors.first('booking[same_slot_all_days]') }}</span>
</div>
<div class="section">
<div class="secton-title">
<span>{{ __('bookingproduct::app.admin.catalog.products.slots') }}</span>
</div>
<div class="section-content">
<slot-list
booking-type="appointment_slot"
:same-slot-all-days="appointment_booking.same_slot_all_days"
:slot-has-quantity="appointment_booking.slot_has_quantity">
</slot-list>
</div>
</div>
</div>
</script>
<script>
Vue.component('appointment-booking', {
template: '#appointment-booking-template',
inject: ['$validator'],
data: function() {
return {
appointment_booking: bookingProduct && bookingProduct.appointment_slot ? bookingProduct.appointment_slot : {
duration: 45,
break_time: 15,
available_every_week: 1,
available_from: '',
available_to: '',
same_slot_all_days: 1,
slot_has_quantity: 1,
slots: []
}
}
}
});
</script>
@endpush

View File

@ -0,0 +1,311 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.table.before', ['product' => $product]) !!}
<default-booking></default-booking>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.table.after', ['product' => $product]) !!}
@section('css')
<style>
.has-control-group .control-group {
width: 50%;
float: left;
}
.has-control-group .control-group:first-child {
padding-right: 10px;
}
.has-control-group .control-group:last-child {
padding-left: 10px;
}
</style>
@stop
@push('scripts')
@parent
<script type="text/x-template" id="default-booking-template">
<div>
<div class="control-group" :class="[errors.has('booking[booking_type]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.type') }}</label>
<select v-validate="'required'" name="booking[booking_type]" v-model="default_booking.booking_type" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.type') }}&quot;">
<option value="many">{{ __('bookingproduct::app.admin.catalog.products.many-bookings-for-one-day') }}</option>
<option value="one">{{ __('bookingproduct::app.admin.catalog.products.one-booking-for-many-days') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[booking_type]')">@{{ errors.first('booking[booking_type]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_from]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-from') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_from]" v-model="default_booking.available_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-from') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_from]')">@{{ errors.first('booking[available_from]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_to]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-to') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_to]" v-model="default_booking.available_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-to') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_to]')">@{{ errors.first('booking[available_to]') }}</span>
</div>
<div v-if="default_booking.booking_type == 'many'">
<div class="control-group" :class="[errors.has('booking[duration]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[duration]" v-model="default_booking.duration" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[duration]')">@{{ errors.first('booking[duration]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[break_time]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.break-time') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[break_time]" v-model="default_booking.break_time" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.break-time') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[break_time]')">@{{ errors.first('booking[break_time]') }}</span>
</div>
</div>
<div class="section">
<div class="secton-title">
<span>{{ __('bookingproduct::app.admin.catalog.products.slots') }}</span>
</div>
<div class="section-content">
<div class="slot-list table" v-if="default_booking.booking_type == 'many'">
<table>
<thead>
<tr>
<th>{{ __('bookingproduct::app.admin.catalog.products.day') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.from') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.to') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.status') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(day, index) in days">
<td>@{{ day }}</td>
<td>
<div class="control-group" :class="[errors.has('booking[slots][' + index + '][from]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="parseInt(slots.many[index].status) ? 'required': ''" :name="'booking[slots][' + index + '][from]'" class="control" v-model="slots.many[index].from" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.from') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has('booking[slots][' + index + '][from]')">
@{{ errors.first('booking[slots][' + index + '][from]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has('booking[slots][' + index + '][to]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="parseInt(slots.many[index].status) ? 'required': ''" :name="'booking[slots][' + index + '][to]'" class="control" v-model="slots.many[index].to" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.to') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has('booking[slots][' + index + '][to]')">
@{{ errors.first('booking[slots][' + index + '][to]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has('booking[slots][' + index + '][status]') ? 'has-error' : '']">
<select v-validate="'required'" :name="'booking[slots][' + index + '][status]'" class="control" v-model="slots.many[index].status" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.status') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.open') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.close') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[slots][' + index + '][status]')">@{{ errors.first('booking[slots][' + index + '][status]') }}</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="slot-list table" v-if="default_booking.booking_type == 'one'">
<table>
<thead>
<tr>
<th>{{ __('bookingproduct::app.admin.catalog.products.from') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.to') }}</th>
<th></th>
</tr>
</thead>
<tbody>
<default-slot-item
v-for="(slot, index) in slots.one"
:key="index"
:slot-item="slot"
:control-name="'booking[slots][' + index + ']'"
@onRemoveSlot="removeSlot($event)">
</default-slot-item>
</tbody>
</table>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px" @click="addSlot()">
{{ __('bookingproduct::app.admin.catalog.products.add-slot') }}
</button>
</div>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="default-slot-item-template">
<tr>
<td class="has-control-group">
<div class="control-group" :class="[errors.has(controlName + '[from_day]') ? 'has-error' : '']">
<select v-validate="'required'" :name="controlName + '[from_day]'" v-model="slotItem.from_day" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.day') }}&quot;">
<option v-for="(day, index) in $parent.days" :value="index">@{{ day }}</option>
</select>
<span class="control-error" v-if="errors.has(controlName + '[from_day]')">@{{ errors.first(controlName + '[from_day]') }}</span>
</div>
<div class="control-group" :class="[errors.has(controlName + '[from]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="'required'" :name="controlName + '[from]'" v-model="slotItem.from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.from') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has(controlName + '[from]')">
@{{ errors.first(controlName + '[from]') }}
</span>
</div>
</td>
<td class="has-control-group">
<div class="control-group" :class="[errors.has(controlName + '[to_day]') ? 'has-error' : '']">
<select v-validate="'required'" :name="controlName + '[to_day]'" v-model="slotItem.to_day" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.day') }}&quot;">
<option v-for="(day, index) in $parent.days" :value="index">@{{ day }}</option>
</select>
<span class="control-error" v-if="errors.has(controlName + '[to_day]')">@{{ errors.first(controlName + '[to_day]') }}</span>
</div>
<div class="control-group" :class="[errors.has(controlName + '[to]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="'required'" :name="controlName + '[to]'" v-model="slotItem.to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.to') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has(controlName + '[to]')">
@{{ errors.first(controlName + '[to]') }}
</span>
</div>
</td>
<td>
<i class="icon remove-icon" @click="removeSlot()"></i>
</td>
</tr>
</script>
<script>
Vue.component('default-booking', {
template: '#default-booking-template',
inject: ['$validator'],
data: function() {
return {
default_booking: bookingProduct && bookingProduct.default_slot ? bookingProduct.default_slot : {
booking_type: 'one',
duration: 45,
break_time: 15,
available_from: '',
available_to: '',
slots: []
},
slots: {
'one': [],
'many': [
{'from': '', 'to': '', 'status': 0},
{'from': '', 'to': '', 'status': 1},
{'from': '', 'to': '', 'status': 1},
{'from': '', 'to': '', 'status': 1},
{'from': '', 'to': '', 'status': 1},
{'from': '', 'to': '', 'status': 1},
{'from': '', 'to': '', 'status': 1}
]
},
days: [
"{{ __('bookingproduct::app.admin.catalog.products.sunday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.monday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.tuesday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.wednesday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.thursday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.friday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.saturday') }}"
]
}
},
created: function() {
if (this.default_booking.booking_type == 'one') {
this.slots['one'] = this.default_booking.slots ? this.default_booking.slots : [];
} else {
if (this.default_booking.slots)
this.slots['many'] = this.default_booking.slots;
}
},
methods: {
addSlot: function () {
this.slots.one.push({ 'from_day': 0, 'from': '', 'to_day': 0, 'to': '' });
},
removeSlot: function(slot) {
let index = this.slots.one.indexOf(slot)
this.slots.one.splice(index, 1)
},
}
});
Vue.component('default-slot-item', {
template: '#default-slot-item-template',
props: ['slotItem', 'controlName'],
inject: ['$validator'],
methods: {
removeSlot: function() {
this.$emit('onRemoveSlot', this.slotItem)
},
}
});
</script>
@endpush

View File

@ -0,0 +1,186 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.event.before', ['product' => $product]) !!}
<event-booking></event-booking>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.event.after', ['product' => $product]) !!}
@push('scripts')
@parent
<script type="text/x-template" id="event-booking-template">
<div>
<div class="control-group" :class="[errors.has('booking[available_from]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.event-start-date') }}</label>
<datetime>
<input type="text" v-validate="'required'" name="booking[available_from]" v-model="event_booking.available_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.event-start-date') }}&quot;"/>
</datetime>
<span class="control-error" v-if="errors.has('booking[available_from]')">@{{ errors.first('booking[available_from]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_to]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.event-end-date') }}</label>
<datetime>
<input type="text" v-validate="'required'" name="booking[available_to]" v-model="event_booking.available_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.event-end-date') }}&quot;"/>
</datetime>
<span class="control-error" v-if="errors.has('booking[available_to]')">@{{ errors.first('booking[available_to]') }}</span>
</div>
<div class="section">
<div class="secton-title">
<span>{{ __('bookingproduct::app.admin.catalog.products.tickets') }}</span>
</div>
<div class="section-content">
<ticket-list :tickets="event_booking.slots"></ticket-list>
</div>
</div>
</div>
</script>
<script type="text/x-template" id="ticket-list-template">
<div class="ticket-list table">
<table>
<thead>
<tr>
<th>{{ __('bookingproduct::app.admin.catalog.products.name') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.price') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.quantity') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.description') }}</th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<ticket-item
v-for="(ticket, index) in tickets"
:key="index"
:ticket-item="ticket"
:control-name="'booking[slots][' + index + ']'"
@onRemoveTicket="removeTicket($event)"
></ticket-item>
</tbody>
</table>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px" @click="addTicket()">
{{ __('bookingproduct::app.admin.catalog.products.add-ticket') }}
</button>
</div>
</script>
<script type="text/x-template" id="ticket-item-template">
<tr>
<td>
<div class="control-group" :class="[errors.has(controlName + '[name]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" :name="controlName + '[name]'" v-model="ticketItem.name" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.name') }}&quot;">
<span class="control-error" v-if="errors.has(controlName + '[name]')">
@{{ errors.first(controlName + '[name]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(controlName + '[price]') ? 'has-error' : '']">
<input type="text" v-validate="'required'" :name="controlName + '[price]'" v-model="ticketItem.price" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.price') }}&quot;">
<span class="control-error" v-if="errors.has(controlName + '[price]')">
@{{ errors.first(controlName + '[price]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(controlName + '[qty]') ? 'has-error' : '']">
<input type="text" v-validate="'required|min_value:0'" :name="controlName + '[qty]'" v-model="ticketItem.qty" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.qty') }}&quot;">
<span class="control-error" v-if="errors.has(controlName + '[qty]')">
@{{ errors.first(controlName + '[qty]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(controlName + '[description]') ? 'has-error' : '']">
<textarea type="text" v-validate="'required'" :name="controlName + '[description]'" v-model="ticketItem.description" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.description') }}&quot;"></textarea>
<span class="control-error" v-if="errors.has(controlName + '[description]')">
@{{ errors.first(controlName + '[description]') }}
</span>
</div>
</td>
<td>
<i class="icon remove-icon" @click="removeTicket()"></i>
</td>
</tr>
</script>
<script>
Vue.component('event-booking', {
template: '#event-booking-template',
inject: ['$validator'],
data: function() {
return {
event_booking: bookingProduct && bookingProduct.event_slot ? bookingProduct.event_slot : {
available_from: '',
available_to: '',
slots: []
}
}
}
});
Vue.component('ticket-list', {
template: '#ticket-list-template',
props: ['tickets'],
inject: ['$validator'],
methods: {
addTicket: function (dayIndex = null) {
this.tickets.push({
'name': '',
'price': '',
'qty': 0,
'description': ''
});
},
removeTicket: function(ticket) {
let index = this.tickets.indexOf(ticket)
this.tickets.splice(index, 1)
},
}
});
Vue.component('ticket-item', {
template: '#ticket-item-template',
props: ['ticketItem', 'controlName'],
inject: ['$validator'],
methods: {
removeTicket: function() {
this.$emit('onRemoveTicket', this.ticketItem)
},
}
});
</script>
@endpush

View File

@ -0,0 +1,151 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.rental.before', ['product' => $product]) !!}
<rental-booking></rental-booking>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.rental.after', ['product' => $product]) !!}
@push('scripts')
@parent
<script type="text/x-template" id="rental-booking-template">
<div>
<div class="control-group" :class="[errors.has('booking[renting_type]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.renting-type') }}</label>
<select v-validate="'required'" name="booking[renting_type]" v-model="rental_booking.renting_type" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.renting-type') }}&quot;">
<option value="daily">{{ __('bookingproduct::app.admin.catalog.products.daily') }}</option>
<option value="hourly">{{ __('bookingproduct::app.admin.catalog.products.hourly') }}</option>
<option value="daily_hourly">{{ __('bookingproduct::app.admin.catalog.products.daily-hourly') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[renting_type]')">@{{ errors.first('booking[renting_type]') }}</span>
</div>
<div v-if="rental_booking.renting_type == 'daily' || rental_booking.renting_type == 'daily_hourly'">
<div class="control-group" :class="[errors.has('booking[daily_price]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.daily-price') }}</label>
<input type="text" v-validate="'required'" name="booking[daily_price]" v-model="rental_booking.daily_price" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.daily-price') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[daily_price]')">@{{ errors.first('booking[daily_price]') }}</span>
</div>
</div>
<div v-if="rental_booking.renting_type == 'hourly' || rental_booking.renting_type == 'daily_hourly'">
<div class="control-group" :class="[errors.has('booking[hourly_price]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.hourly-price') }}</label>
<input type="text" v-validate="'required'" name="booking[hourly_price]" v-model="rental_booking.hourly_price" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.hourly-price') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[hourly_price]')">@{{ errors.first('booking[hourly_price]') }}</span>
</div>
</div>
<div class="control-group" :class="[errors.has('booking[available_every_week]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}</label>
<select v-validate="'required'" name="booking[available_every_week]" v-model="rental_booking.available_every_week" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[available_every_week]')">@{{ errors.first('booking[available_every_week]') }}</span>
</div>
<div v-if="! parseInt(rental_booking.available_every_week)">
<div class="control-group" :class="[errors.has('booking[available_from]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-from') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_from]" v-model="rental_booking.available_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-from') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_from]')">@{{ errors.first('booking[available_from]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_to]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-to') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_to]" v-model="rental_booking.available_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-to') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_to]')">@{{ errors.first('booking[available_to]') }}</span>
</div>
</div>
<div v-if="rental_booking.renting_type == 'hourly' || rental_booking.renting_type == 'daily_hourly'">
<div class="control-group" :class="[errors.has('booking[slot_has_quantity]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.slot-has-quantity') }}</label>
<select v-validate="'required'" name="booking[slot_has_quantity]" v-model="rental_booking.slot_has_quantity" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.slot-has-quantity') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot_has_quantity]')">@{{ errors.first('booking[slot_has_quantity]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[same_slot_all_days]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}</label>
<select v-validate="'required'" name="booking[same_slot_all_days]" v-model="rental_booking.same_slot_all_days" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[same_slot_all_days]')">@{{ errors.first('booking[same_slot_all_days]') }}</span>
</div>
<div class="section">
<div class="secton-title">
<span>{{ __('bookingproduct::app.admin.catalog.products.slots') }}</span>
</div>
<div class="section-content">
<slot-list
booking-type="rental_slot"
:same-slot-all-days="rental_booking.same_slot_all_days"
:slot-has-quantity="rental_booking.slot_has_quantity">
</slot-list>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('rental-booking', {
template: '#rental-booking-template',
inject: ['$validator'],
data: function() {
return {
rental_booking: bookingProduct && bookingProduct.rental_slot ? bookingProduct.rental_slot : {
renting_type: 'daily',
daily_price: '',
hourly_price: '',
available_every_week: 1,
available_from: '',
available_to: '',
same_slot_all_days: 1,
slot_has_quantity: 1,
slots: []
}
}
}
});
</script>
@endpush

View File

@ -0,0 +1,211 @@
<script type="text/x-template" id="slot-list-template">
<div>
<div class="slot-list table" v-if="parseInt(sameSlotAllDays)">
<table>
<thead>
<tr>
<th>{{ __('bookingproduct::app.admin.catalog.products.from') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.to') }}</th>
<th v-if="parseInt(slotHasQuantity)">{{ __('bookingproduct::app.admin.catalog.products.qty') }}</th>
<th class="actions"></th>
</tr>
</thead>
<tbody>
<slot-item
v-for="(slot, index) in slots['same_for_week']"
:key="index"
:slot-item="slot"
:control-name="'booking[slots][' + index + ']'"
:slot-has-quantity="slotHasQuantity"
@onRemoveSlot="removeSlot($event)">
</slot-item>
</tbody>
</table>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px" @click="addSlot()">
{{ __('bookingproduct::app.admin.catalog.products.add-slot') }}
</button>
</div>
<div class="slot-list table" v-else>
<accordian
v-for="(day, dayIndex) in week_days"
:key="dayIndex"
:title="day"
:active="false">
<div slot="header">
<i class="icon expand-icon left"></i>
<h1>@{{ day }}</h1>
</div>
<div slot="body">
<table>
<thead>
<tr>
<th>{{ __('bookingproduct::app.admin.catalog.products.from') }}</th>
<th>{{ __('bookingproduct::app.admin.catalog.products.to') }}</th>
<th v-if="parseInt(slotHasQuantity)">{{ __('bookingproduct::app.admin.catalog.products.qty') }}</th>
<th class="actions"></th>
</tr>
</thead>
<tbody v-if="slots['different_for_week'][dayIndex] && slots['different_for_week'][dayIndex].length">
<slot-item
v-for="(slot, slotIndex) in slots['different_for_week'][dayIndex]"
:key="dayIndex + '_' + slotIndex"
:slot-item="slot"
:control-name="'booking[slots][' + dayIndex + '][' + slotIndex + ']'"
:slot-has-quantity="slotHasQuantity"
@onRemoveSlot="removeSlot($event, dayIndex)"
></slot-item>
</tbody>
</table>
<button type="button" class="btn btn-lg btn-primary" style="margin-top: 20px" @click="addSlot(dayIndex)">
{{ __('bookingproduct::app.admin.catalog.products.add-slot') }}
</button>
</div>
</accordian>
</div>
</div>
</script>
<script type="text/x-template" id="slot-item-template">
<tr>
<td>
<div class="control-group" :class="[errors.has(controlName + '[from]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="'required'" :name="controlName + '[from]'" v-model="slotItem.from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.from') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has(controlName + '[from]')">
@{{ errors.first(controlName + '[from]') }}
</span>
</div>
</td>
<td>
<div class="control-group" :class="[errors.has(controlName + '[to]') ? 'has-error' : '']">
<time-component>
<input type="text" v-validate="'required'" :name="controlName + '[to]'" v-model="slotItem.to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.to') }}&quot;">
</time-component>
<span class="control-error" v-if="errors.has(controlName + '[to]')">
@{{ errors.first(controlName + '[to]') }}
</span>
</div>
</td>
<td v-if="parseInt(slotHasQuantity)">
<div class="control-group" :class="[errors.has(controlName + '[qty]') ? 'has-error' : '']">
<input type="text" v-validate="'required|min_value:0'" :name="controlName + '[qty]'" v-model="slotItem.qty" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.qty') }}&quot;">
<span class="control-error" v-if="errors.has(controlName + '[qty]')">
@{{ errors.first(controlName + '[qty]') }}
</span>
</div>
</td>
<td>
<i class="icon remove-icon" @click="removeSlot()"></i>
</td>
</tr>
</script>
<script>
Vue.component('slot-list', {
template: '#slot-list-template',
props: ['bookingType', 'sameSlotAllDays', 'slotHasQuantity'],
inject: ['$validator'],
data: function() {
return {
slots: {
'same_for_week': [],
'different_for_week': [[], [], [], [], [], [], []]
},
week_days: [
"{{ __('bookingproduct::app.admin.catalog.products.sunday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.monday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.tuesday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.wednesday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.thursday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.friday') }}",
"{{ __('bookingproduct::app.admin.catalog.products.saturday') }}"
]
}
},
created: function() {
if (! bookingProduct || ! bookingProduct[this.bookingType].slots || ! bookingProduct[this.bookingType].slots)
return;
if (bookingProduct[this.bookingType].same_slot_all_days) {
this.slots['same_for_week'] = bookingProduct[this.bookingType].slots;
} else {
this.slots['different_for_week'] = bookingProduct[this.bookingType].slots;
}
},
methods: {
addSlot: function (dayIndex = null) {
if (dayIndex !== null) {
if (this.slots['different_for_week'][dayIndex] == undefined)
this.slots['different_for_week'][dayIndex] = [];
var slot = {
'from': '',
'to': '',
'qty': 0
};
this.slots['different_for_week'][dayIndex].push(slot)
Vue.set(this.slots['different_for_week'], dayIndex, this.slots['different_for_week'][dayIndex])
} else {
var slot = {
'from': '',
'to': '',
'qty': 0
};
this.slots['same_for_week'].push(slot);
}
},
removeSlot: function(slot, dayIndex = null) {
if (dayIndex != null) {
let index = this.slots['different_for_week'][dayIndex].indexOf(slot)
this.slots['different_for_week'][dayIndex].splice(index, 1)
} else {
let index = this.slots['same_for_week'].indexOf(slot)
this.slots['same_for_week'].splice(index, 1)
}
},
}
});
Vue.component('slot-item', {
template: '#slot-item-template',
props: ['slotItem', 'controlName', 'slotHasQuantity'],
inject: ['$validator'],
methods: {
removeSlot: function() {
this.$emit('onRemoveSlot', this.slotItem)
},
}
});
</script>

View File

@ -0,0 +1,150 @@
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.table.before', ['product' => $product]) !!}
<table-booking></table-booking>
{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.booking.table.after', ['product' => $product]) !!}
@push('scripts')
@parent
<script type="text/x-template" id="table-booking-template">
<div>
<div class="control-group" :class="[errors.has('booking[price_type]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.charged-per') }}</label>
<select v-validate="'required'" name="booking[price_type]" v-model="table_booking.price_type" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.charged-per') }}&quot;">
<option value="guest">{{ __('bookingproduct::app.admin.catalog.products.guest') }}</option>
<option value="table">{{ __('bookingproduct::app.admin.catalog.products.table') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[price_type]')">@{{ errors.first('booking[price_type]') }}</span>
</div>
<div v-if="table_booking.price_type == 'table'" class="control-group" :class="[errors.has('booking[guest_limit]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.guest-limit') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[guest_limit]" v-model="table_booking.guest_limit" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.guest-limit') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[guest_limit]')">@{{ errors.first('booking[guest_limit]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[duration]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[duration]" v-model="table_booking.duration" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.slot-duration') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[duration]')">@{{ errors.first('booking[duration]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[break_time]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.break-time') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[break_time]" v-model="table_booking.break_time" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.break-time') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[break_time]')">@{{ errors.first('booking[break_time]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[prevent_scheduling_before]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.prevent-scheduling-before') }}</label>
<input type="text" v-validate="'required|min_value:1'" name="booking[prevent_scheduling_before]" v-model="table_booking.prevent_scheduling_before" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.prevent-scheduling-before') }}&quot;"/>
<span class="control-error" v-if="errors.has('booking[prevent_scheduling_before]')">@{{ errors.first('booking[prevent_scheduling_before]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_every_week]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}</label>
<select v-validate="'required'" name="booking[available_every_week]" v-model="table_booking.available_every_week" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-every-week') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[available_every_week]')">@{{ errors.first('booking[available_every_week]') }}</span>
</div>
<div v-if="! parseInt(table_booking.available_every_week)">
<div class="control-group" :class="[errors.has('booking[available_from]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-from') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_from]" v-model="table_booking.available_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-from') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_from]')">@{{ errors.first('booking[available_from]') }}</span>
</div>
<div class="control-group" :class="[errors.has('booking[available_to]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.available-to') }}</label>
<date>
<input type="text" v-validate="'required'" name="booking[available_to]" v-model="table_booking.available_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.available-to') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[available_to]')">@{{ errors.first('booking[available_to]') }}</span>
</div>
</div>
<div class="control-group" :class="[errors.has('booking[same_slot_all_days]') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}</label>
<select v-validate="'required'" name="booking[same_slot_all_days]" v-model="table_booking.same_slot_all_days" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.admin.catalog.products.same-slot-all-days') }}&quot;">
<option value="1">{{ __('bookingproduct::app.admin.catalog.products.yes') }}</option>
<option value="0">{{ __('bookingproduct::app.admin.catalog.products.no') }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[same_slot_all_days]')">@{{ errors.first('booking[same_slot_all_days]') }}</span>
</div>
<div class="section">
<div class="secton-title">
<span>{{ __('bookingproduct::app.admin.catalog.products.slots') }}</span>
</div>
<div class="section-content">
<slot-list
booking-type="table_slot"
:same-slot-all-days="table_booking.same_slot_all_days">
</slot-list>
</div>
</div>
</div>
</script>
<script>
Vue.component('table-booking', {
template: '#table-booking-template',
inject: ['$validator'],
data: function() {
return {
table_booking: bookingProduct && bookingProduct.table_slot ? bookingProduct.table_slot : {
price_type: 'guest',
guest_limit: 2,
duration: 45,
break_time: 15,
prevent_scheduling_before: 0,
available_every_week: 1,
available_from: '',
available_to: '',
same_slot_all_days: 1,
slots: []
}
}
}
});
</script>
@endpush

View File

@ -0,0 +1,60 @@
@if ($product->type == 'booking')
@if ($bookingProduct = app('\Webkul\BookingProduct\Repositories\BookingProductRepository')->findOneByField('product_id', $product->product_id))
@push('css')
<link rel="stylesheet" href="{{ bagisto_asset('css/booking-product.css') }}">
@endpush
<booking-information></booking-information>
@push('scripts')
<script type="text/x-template" id="booking-information-template">
<div class="booking-information">
<div class="booking-info-row">
<span class="icon bp-location-icon"></span>
<span class="title">{{ __('bookingproduct::app.shop.products.location') }}</span>
<span class="value">New Ashok Nagar</span>
<a href="" target="_blank">View on Map</a>
</div>
<div class="booking-info-row">
<span class="icon bp-phone-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.contact') }} :
<a href="">{{ __('bookingproduct::app.shop.products.email') }}</a>
</span>
</div>
@include ('bookingproduct::shop.products.view.booking.' . $bookingProduct->type, ['bookingProduct' => $bookingProduct])
</div>
</script>
<script>
Vue.component('booking-information', {
template: '#booking-information-template',
inject: ['$validator'],
data: function() {
return {
showDaysAvailability: false
}
},
created: function() {
}
});
</script>
@endpush
@endif
@endif

View File

@ -0,0 +1,55 @@
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.slot-duration') }} :
{{ __('bookingproduct::app.shop.products.slot-duration-in-minutes', ['minutes' => 30]) }}
</span>
</div>
@inject ('bookingSlotHelper', 'Webkul\BookingProduct\Helpers\AppointmentSlot')
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.today-availability') }}
</span>
<span class="value">
{!! $bookingSlotHelper->getTodaySlotsHtml($bookingProduct) !!}
</span>
<div class="toggle" @click="showDaysAvailability = ! showDaysAvailability">
Show for all days
<i class="icon arrow-down-icon"></i>
</div>
<div class="days-availability" v-show="showDaysAvailability">
<table>
<tbody>
@foreach ($bookingSlotHelper->getWeekSlotDurations($bookingProduct) as $day)
<tr>
<td>{{ $day['name'] }}</td>
<td>
@if ($day['slots'] && count($day['slots']))
@foreach ($day['slots'] as $slot)
{{ $slot['from'] . ' - ' . $slot['to'] }}</br>
@endforeach
@else
<span class="text-danger">{{ __('bookingproduct::app.shop.products.closed') }}</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@include ('bookingproduct::shop.products.view.booking.slots', ['bookingProduct' => $bookingProduct])

View File

@ -0,0 +1,10 @@
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.slot-duration') }} :
{{ __('bookingproduct::app.shop.products.slot-duration-in-minutes', ['minutes' => 30]) }}
</span>
</div>
@include ('bookingproduct::shop.products.view.booking.slots', ['bookingProduct' => $bookingProduct])

View File

@ -0,0 +1,80 @@
@inject ('bookingSlotHelper', 'Webkul\BookingProduct\Helpers\EventSlot')
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.event-on') }}
</span>
<span class="value">
{!! $bookingSlotHelper->getEventDate($bookingProduct) !!}
</span>
</div>
<event-tickets></event-tickets>
@push('scripts')
<script type="text/x-template" id="event-tickets-template">
<div class="book-slots">
<label style="font-weight: 600">{{ __('bookingproduct::app.shop.products.book-your-ticket') }}</label>
<div class="ticket-list">
<div class="ticket-item" v-for="(ticket, index) in tickets">
<div class="ticket-info">
<div class="ticket-name">
@{{ ticket.name }}
</div>
<div class="ticket-price">
@{{ ticket.formated_price_text }}
</div>
</div>
<div class="ticket-quantity">
<div class="control-group" :class="[errors.has('booking[qty][' + index + ']') ? 'has-error' : '']">
<label class="required">{{ __('bookingproduct::app.shop.products.number-of-tickets') }}</label>
<input type="text" v-validate="'required'" :name="'booking[qty][' + index + ']'" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.number-of-tickets') }}&quot;" style="width: 100%"/>
<span class="control-error" v-if="errors.has('booking[qty][' + index + ']')">@{{ errors.first('booking[qty][' + index + ']') }}</span>
</div>
</div>
<p>@{{ ticket.description }}</p>
</div>
</div>
<div class="ticket-total">
<!--<div class="total-tickets">
{{ __('bookingproduct::app.shop.products.total-tickets') }} - 1
</div>-->
<div class="ticket-base-price">
{{ __('bookingproduct::app.shop.products.base-price') }} - $100
<p>{{ __('bookingproduct::app.shop.products.base-price-info') }}</p>
</div>
<!--<div class="ticket-total-price">
{{ __('bookingproduct::app.shop.products.total-price') }} - $100
</div>-->
</div>
</div>
</script>
<script>
Vue.component('event-tickets', {
template: '#event-tickets-template',
inject: ['$validator'],
data: function() {
return {
tickets: @json($bookingSlotHelper->getTickets($bookingProduct)),
}
}
});
</script>
@endpush

View File

@ -0,0 +1,153 @@
<rental-slots></rental-slots>
@push('scripts')
<script type="text/x-template" id="rental-slots-template">
<div class="book-slots">
<h3>{{ __('bookingproduct::app.shop.products.rent-an-item') }}</h3>
<div v-if="renting_type == 'daily_hourly'">
<div class="control-group">
<label>{{ __('bookingproduct::app.shop.products.choose-rent-option') }}</label>
<span class="radio">
<input type="radio" id="daily-renting-type" name="booking[renting_type]" value="daily" v-model="sub_renting_type">
<label class="radio-view" for="daily-renting-type"></label>
{{ __('bookingproduct::app.shop.products.daily-basis') }}
</span>
<span class="radio">
<input type="radio" id="hourly-renting-type" name="booking[renting_type]" value="hourly" v-model="sub_renting_type">
<label class="radio-view" for="hourly-renting-type"></label>
{{ __('bookingproduct::app.shop.products.hourly-basis') }}
</span>
</div>
</div>
<div v-if="renting_type != 'daily' && sub_renting_type == 'hourly'">
<div>
<label>{{ __('bookingproduct::app.shop.products.select-slot') }}</label>
<div class="control-group-container">
<div class="control-group date" :class="[errors.has('booking[date]') ? 'has-error' : '']">
<date @onChange="dateSelected($event)">
<input type="text" v-validate="'required'" name="booking[date]" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.date') }}&quot;" placeholder="{{ __('bookingproduct::app.shop.products.select-date') }}" data-min-date="today"/>
</date>
<span class="control-error" v-if="errors.has('booking[date]')">@{{ errors.first('booking[date]') }}</span>
</div>
<div class="control-group slots" :class="[errors.has('booking[slot]') ? 'has-error' : '']">
<select v-validate="'required'" name="booking[slot]" v-model="selected_slot" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.slot') }}&quot;">
<option value="">{{ __('bookingproduct::app.shop.products.select-time-slot') }}</option>
<option v-for="(slot, index) in slots" :value="index">@{{ slot.time }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot]')">@{{ errors.first('booking[slot]') }}</span>
</div>
</div>
</div>
<div v-if="slots[selected_slot] && slots[selected_slot]['slots'].length">
<label>{{ __('bookingproduct::app.shop.products.select-rent-time') }}</label>
<div class="control-group-container">
<div class="control-group slots" :class="[errors.has('booking[slot][from]') ? 'has-error' : '']">
<select v-validate="'required'" name="booking[slot][from]" v-model="slot_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.slot') }}&quot;">
<option value="">{{ __('bookingproduct::app.shop.products.select-time-slot') }}</option>
<option v-for="slot in slots[selected_slot]['slots']" :value="slot.from_timestamp">
@{{ slot.from }}
</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot][from]')">@{{ errors.first('booking[slot][from]') }}</span>
</div>
<div class="control-group slots" :class="[errors.has('booking[slot][to]') ? 'has-error' : '']">
<select v-validate="'required'" name="booking[slot][to]" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.slot') }}&quot;">
<option value="">{{ __('bookingproduct::app.shop.products.select-time-slot') }}</option>
<option v-for="slot in slots[selected_slot]['slots']" :value="slot.to_timestamp" v-if="slot_from < slot.to_timestamp">
@{{ slot.to }}
</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot][to]')">@{{ errors.first('booking[slot][to]') }}</span>
</div>
</div>
</div>
</div>
<div v-else>
<label>{{ __('bookingproduct::app.shop.products.select-date') }}</label>
<div class="control-group-container">
<div class="control-group date" :class="[errors.has('booking[date_from]') ? 'has-error' : '']">
<date @onChange="dateSelected($event)">
<input type="text" v-validate="'required'" name="booking[date_from]" v-model="date_from" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.from') }}&quot;" placeholder="{{ __('bookingproduct::app.shop.products.from') }}" data-min-date="today"/>
</date>
<span class="control-error" v-if="errors.has('booking[date_from]')">@{{ errors.first('booking[date_from]') }}</span>
</div>
<div class="control-group date" :class="[errors.has('booking[date_to]') ? 'has-error' : '']">
<date @onChange="dateSelected($event)">
<input type="text" v-validate="'required'" name="booking[date_to]" v-model="date_to" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.to') }}&quot;" placeholder="{{ __('bookingproduct::app.shop.products.to') }}"/>
</date>
<span class="control-error" v-if="errors.has('booking[date_to]')">@{{ errors.first('booking[date_to]') }}</span>
</div>
</div>
</div>
</div>
</script>
<script>
Vue.component('rental-slots', {
template: '#rental-slots-template',
inject: ['$validator'],
data: function() {
return {
renting_type: "{{ $bookingProduct->rental_slot->renting_type }}",
sub_renting_type: 'hourly',
slots: [],
selected_slot: '',
slot_from: 0,
date_from: '',
date_to: ''
}
},
methods: {
dateSelected: function(date) {
var this_this = this;
this.$http.get("{{ route('booking_product.slots.index', $bookingProduct->id) }}", {params: {date: date}})
.then (function(response) {
this_this.selected_slot = '';
this_this.slot_from = 0;
this_this.slots = response.data.data;
})
.catch (function (error) {})
}
}
});
</script>
@endpush

View File

@ -0,0 +1,58 @@
<book-slots></book-slots>
@push('scripts')
<script type="text/x-template" id="book-slots-template">
<div class="book-slots">
<label>{{ $title ?? __('bookingproduct::app.shop.products.book-an-appointment') }} :</label>
<div class="control-group-container">
<div class="control-group date" :class="[errors.has('booking[date]') ? 'has-error' : '']">
<date @onChange="dateSelected($event)">
<input type="text" v-validate="'required'" name="booking[date]" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.date') }}&quot;"/>
</date>
<span class="control-error" v-if="errors.has('booking[date]')">@{{ errors.first('booking[date]') }}</span>
</div>
<div class="control-group slots" :class="[errors.has('booking[slot]') ? 'has-error' : '']">
<select v-validate="'required'" name="booking[slot]" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.slot') }}&quot;">
<option v-for="slot in slots" :value="slot.timestamp">@{{ slot.from + ' - ' + slot.to }}</option>
</select>
<span class="control-error" v-if="errors.has('booking[slot]')">@{{ errors.first('booking[slot]') }}</span>
</div>
</div>
</div>
</script>
<script>
Vue.component('book-slots', {
template: '#book-slots-template',
inject: ['$validator'],
data: function() {
return {
slots: []
}
},
methods: {
dateSelected: function(date) {
var this_this = this;
this.$http.get("{{ route('booking_product.slots.index', $bookingProduct->id) }}", {params: {date: date}})
.then (function(response) {
this_this.slots = response.data.data;
})
.catch (function (error) {})
}
}
});
</script>
@endpush

View File

@ -0,0 +1,69 @@
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.slot-duration') }} :
{{ __('bookingproduct::app.shop.products.slot-duration-in-minutes', ['minutes' => 30]) }}
</span>
</div>
@inject ('bookingSlotHelper', 'Webkul\BookingProduct\Helpers\TableSlot')
<div class="booking-info-row">
<span class="icon bp-slot-icon"></span>
<span class="title">
{{ __('bookingproduct::app.shop.products.today-availability') }}
</span>
<span class="value">
{!! $bookingSlotHelper->getTodaySlotsHtml($bookingProduct) !!}
</span>
<div class="toggle" @click="showDaysAvailability = ! showDaysAvailability">
Show for all days
<i class="icon arrow-down-icon"></i>
</div>
<div class="days-availability" v-show="showDaysAvailability">
<table>
<tbody>
@foreach ($bookingSlotHelper->getWeekSlotDurations($bookingProduct) as $day)
<tr>
<td>{{ $day['name'] }}</td>
<td>
@if ($day['slots'] && count($day['slots']))
@foreach ($day['slots'] as $slot)
{{ $slot['from'] . ' - ' . $slot['to'] }}</br>
@endforeach
@else
<span class="text-danger">{{ __('bookingproduct::app.shop.products.closed') }}</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@include ('bookingproduct::shop.products.view.booking.slots', [
'bookingProduct' => $bookingProduct,
'title' => __('bookingproduct::app.shop.products.book-a-table')
])
<div class="control-group" :class="[errors.has('booking[qty]') ? 'has-error' : '']">
<label>{{ __('bookingproduct::app.shop.products.number-of-tables') }}</label>
<input type="text" v-validate="'required'" name="booking[qty]" class="control" data-vv-as="&quot;{{ __('bookingproduct::app.shop.products.number-of-tables') }}&quot;" style="width: 100%"/>
<span class="control-error" v-if="errors.has('booking[qty]')">@{{ errors.first('booking[qty]') }}</span>
</div>
<div class="control-group">
<label>{{ __('bookingproduct::app.shop.products.special-notes') }}</label>
<textarea name="booking[note]" class="control" style="width: 100%"/>
</div>

View File

@ -0,0 +1,169 @@
<?php
namespace Webkul\BookingProduct\Type;
use Webkul\Attribute\Repositories\AttributeRepository;
use Webkul\Product\Repositories\ProductRepository;
use Webkul\Product\Repositories\ProductAttributeValueRepository;
use Webkul\Product\Repositories\ProductInventoryRepository;
use Webkul\Product\Repositories\ProductImageRepository;
use Webkul\Product\Helpers\ProductImage;
use Webkul\BookingProduct\Helpers\Booking as BookingHelper;
use Webkul\Product\Type\AbstractType;
/**
* Class Booking.
*
* @author Jitendra Singh <jitendra@webkul.com>
* @copyright 2018 Webkul Software Pvt Ltd (http://www.webkul.com)
*/
class Booking extends AbstractType
{
/**
* Booking helper instance
*
* @var Booking
*/
protected $bookingHelper;
/**
* Skip attribute for virtual product type
*
* @var array
*/
protected $skipAttributes = ['width', 'height', 'depth', 'weight'];
/**
* Show quantity box
*
* @var boolean
*/
protected $showQuantityBox = true;
/**
* @var array
*/
protected $additionalViews = [
'admin::catalog.products.accordians.images',
'admin::catalog.products.accordians.categories',
'bookingproduct::admin.catalog.products.accordians.booking',
'admin::catalog.products.accordians.product-links'
];
/**
* Create a new product type instance.
*
* @param Webkul\Attribute\Repositories\AttributeRepository $attributeRepository
* @param Webkul\Product\Repositories\ProductRepository $productRepository
* @param Webkul\Product\Repositories\ProductAttributeValueRepository $attributeValueRepository
* @param Webkul\Product\Repositories\ProductInventoryRepository $productInventoryRepository
* @param Webkul\Product\Repositories\ProductImageRepository $productImageRepository
* @param Webkul\BookingProduct\Helpers\BookingHelper $bookingHelper
* @param Webkul\Product\Helpers\ProductImage $productImageHelper
* @return void
*/
public function __construct(
AttributeRepository $attributeRepository,
ProductRepository $productRepository,
ProductAttributeValueRepository $attributeValueRepository,
ProductInventoryRepository $productInventoryRepository,
ProductImageRepository $productImageRepository,
BookingHelper $bookingHelper,
ProductImage $productImageHelper
)
{
parent::__construct(
$attributeRepository,
$productRepository,
$attributeValueRepository,
$productInventoryRepository,
$productImageRepository,
$productImageHelper
);
$this->bookingHelper = $bookingHelper;
}
/**
* Return true if this product type is saleable
*
* @return array
*/
public function isSaleable()
{
if (! $this->product->status)
return false;
return true;
}
/**
* Return true if this product can have inventory
*
* @return array
*/
public function isStockable()
{
return false;
}
/**
* Return true if item can be moved to cart from wishlist
*
* @param CartItem $item
* @return boolean
*/
public function canBeMovedFromWishlistToCart($item)
{
return true;
}
/**
* Add product. Returns error message if can't prepare product.
*
* @param array $data
* @return array
*/
public function prepareForCart($data)
{
$products = parent::prepareForCart($data);
dd($products);
}
/**
*
* @param array $options1
* @param array $options2
* @return boolean
*/
public function compareOptions($options1, $options2)
{
if ($this->product->id != $options2['product_id'])
return false;
return $options1['booking'] == $options2['booking'];
}
/**
* Returns additional information for items
*
* @param array $data
* @return array
*/
public function getAdditionalOptions($data)
{
$data['attributes'] = [
[
'attribute_name' => 'From',
'option_id' => 0,
'option_label' => "",
], [
'attribute_name' => 'To',
'option_id' => 0,
'option_label' => "",
]];
return $data;
}
}

View File

@ -0,0 +1,31 @@
const mix = require("laravel-mix");
if (mix == 'undefined') {
const { mix } = require("laravel-mix");
}
require("laravel-mix-merge-manifest");
if (mix.inProduction()) {
var publicPath = 'publishable/assets';
} else {
var publicPath = "../../../public/themes/default/assets";
}
mix.setPublicPath(publicPath).mergeManifest();
mix.disableNotifications();
mix.copyDirectory(__dirname + "/src/Resources/assets/images", publicPath + "/images")
.sass(__dirname + "/src/Resources/assets/sass/app.scss", "css/booking-product.css")
.options({
processCssUrls: false
});
if (!mix.inProduction()) {
mix.sourceMaps();
}
if (mix.inProduction()) {
mix.version();
}