changes, global search (clients, cleintCases)

This commit is contained in:
Simon Pocrnjič 2024-11-19 12:49:16 +01:00
parent c45751c1e2
commit 3ae70bf340
37 changed files with 1888 additions and 229 deletions

View File

@ -0,0 +1,25 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Post;
class ImportPosts extends Command
{
protected $signature = 'import:posts';
protected $description = 'Import posts into Algolia without clearing the index';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$posts = Post::all();
$posts->searchable();
$this->info('Posts have been imported into Algolia.');
}
}

View File

@ -24,7 +24,7 @@ public function index(ClientCase $clientCase, Request $request)
) )
->where('active', 1) ->where('active', 1)
->orderByDesc('created_at') ->orderByDesc('created_at')
->paginate(15) ->paginate(15, ['*'], 'client-cases-page')
->withQueryString(), ->withQueryString(),
'filters' => $request->only(['search']) 'filters' => $request->only(['search'])
]); ]);
@ -164,7 +164,7 @@ public function show(ClientCase $clientCase)
->orderByDesc('created_at')->get(), ->orderByDesc('created_at')->get(),
'activities' => $case->activities()->with(['action', 'decision']) 'activities' => $case->activities()->with(['action', 'decision'])
->orderByDesc('created_at') ->orderByDesc('created_at')
->paginate(15), ->paginate(20, ['*'], 'activities'),
'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(), 'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(),
'actions' => \App\Models\Action::with('decisions')->get() 'actions' => \App\Models\Action::with('decisions')->get()
]); ]);

View File

@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StorePostRequest $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Post $post)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Post $post)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdatePostRequest $request, Post $post)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Post $post)
{
//
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -6,11 +6,13 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Laravel\Scout\Searchable;
class Action extends Model class Action extends Model
{ {
/** @use HasFactory<\Database\Factories\ActionFactory> */ /** @use HasFactory<\Database\Factories\ActionFactory> */
use HasFactory; use HasFactory;
use Searchable;
public function decisions(): BelongsToMany public function decisions(): BelongsToMany
{ {

View File

@ -7,12 +7,15 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Searchable;
class Client extends Model class Client extends Model
{ {
/** @use HasFactory<\Database\Factories\ClientFactory> */ /** @use HasFactory<\Database\Factories\ClientFactory> */
use HasFactory; use HasFactory;
use Uuid; use Uuid;
use Searchable;
protected $fillable = [ protected $fillable = [
'person_id' 'person_id'
@ -23,6 +26,26 @@ class Client extends Model
'person_id', 'person_id',
]; ];
protected function makeAllSearchableUsing(Builder $query): Builder
{
return $query->with('person');
}
public function toSearchableArray(): array
{
$person = [
'full_name' => $this->person->full_name,
'addresses' => $this->person->addresses,
'phones' => $this->person->phones
];
return [
'person' => $person
];
}
public function person(): BelongsTo public function person(): BelongsTo
{ {
return $this->belongsTo(\App\Models\Person\Person::class); return $this->belongsTo(\App\Models\Person\Person::class);

View File

@ -7,12 +7,15 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Searchable;
class ClientCase extends Model class ClientCase extends Model
{ {
/** @use HasFactory<\Database\Factories\ClientCaseFactory> */ /** @use HasFactory<\Database\Factories\ClientCaseFactory> */
use HasFactory; use HasFactory;
use Uuid; use Uuid;
use Searchable;
protected $fillable = [ protected $fillable = [
'client_id' 'client_id'
@ -24,6 +27,20 @@ class ClientCase extends Model
'person_id' 'person_id'
]; ];
protected function makeAllSearchableUsing(Builder $query): Builder
{
return $query->with('person');
}
public function toSearchableArray(): array
{
return [
'person' => $this->person,
'addresses' => $this->person->addresses,
'phones' => $this->person->phones
];
}
public function client(): BelongsTo public function client(): BelongsTo
{ {
return $this->belongsTo(\App\Models\Client::class); return $this->belongsTo(\App\Models\Client::class);

View File

@ -8,7 +8,9 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
use Laravel\Scout\Searchable;
class Person extends Model class Person extends Model
{ {
@ -16,6 +18,7 @@ class Person extends Model
/** @use HasFactory<\Database\Factories\Person/PersonFactory> */ /** @use HasFactory<\Database\Factories\Person/PersonFactory> */
use HasFactory; use HasFactory;
use Uuid; use Uuid;
use Searchable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -52,6 +55,22 @@ protected static function booted(){
}); });
} }
protected function makeAllSearchableUsing(Builder $query): Builder
{
return $query->with(['addresses', 'phones']);
}
public function toSearchableArray(): array
{
return [
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'full_name' => $this->full_name,
'addresses' => $this->addresses,
'phones' => $this->phones
];
}
public function phones(): HasMany public function phones(): HasMany
{ {

View File

@ -5,11 +5,13 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Scout\Searchable;
class PersonAddress extends Model class PersonAddress extends Model
{ {
/** @use HasFactory<\Database\Factories\Person/PersonAddressFactory> */ /** @use HasFactory<\Database\Factories\Person/PersonAddressFactory> */
use HasFactory; use HasFactory;
use Searchable;
protected $fillable = [ protected $fillable = [
'address', 'address',
@ -26,6 +28,14 @@ class PersonAddress extends Model
'deleted' 'deleted'
]; ];
public function toSearchableArray(): array
{
return [
'address' => $this->address,
'country' => $this->country
];
}
protected static function booted(){ protected static function booted(){
static::creating(function (PersonAddress $address) { static::creating(function (PersonAddress $address) {
$address->user_id = auth()->id(); $address->user_id = auth()->id();

View File

@ -6,11 +6,13 @@
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Laravel\Scout\Searchable;
class PersonPhone extends Model class PersonPhone extends Model
{ {
/** @use HasFactory<\Database\Factories\Person/PersonPhoneFactory> */ /** @use HasFactory<\Database\Factories\Person/PersonPhoneFactory> */
use HasFactory; use HasFactory;
use Searchable;
protected $fillable = [ protected $fillable = [
'nu', 'nu',
@ -27,6 +29,13 @@ class PersonPhone extends Model
'deleted' 'deleted'
]; ];
public function toSearchableArray(): array
{
return [
'nu' => $this->nu
];
}
protected static function booted(){ protected static function booted(){
static::creating(function (PersonPhone $personPhone) { static::creating(function (PersonPhone $personPhone) {
if(!isset($personPhone->user_id)){ if(!isset($personPhone->user_id)){

18
app/Models/Post.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use HasFactory, Searchable;
public function toSearchableArray()
{
$array = $this->toArray();
return $array;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;
class PostPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
//
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Post $post): bool
{
//
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
//
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Post $post): bool
{
//
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Post $post): bool
{
//
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Post $post): bool
{
//
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Post $post): bool
{
//
}
}

View File

@ -6,13 +6,17 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"algolia/scout-extended": "3.1",
"arielmejiadev/larapex-charts": "^2.1", "arielmejiadev/larapex-charts": "^2.1",
"diglactic/laravel-breadcrumbs": "^9.0", "diglactic/laravel-breadcrumbs": "^9.0",
"http-interop/http-factory-guzzle": "^1.2",
"inertiajs/inertia-laravel": "^1.0", "inertiajs/inertia-laravel": "^1.0",
"laravel/framework": "^11.9", "laravel/framework": "^11.9",
"laravel/jetstream": "^5.2", "laravel/jetstream": "^5.2",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/scout": "^10.11",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"meilisearch/meilisearch-php": "^1.11",
"robertboes/inertia-breadcrumbs": "^0.6.0", "robertboes/inertia-breadcrumbs": "^0.6.0",
"tightenco/ziggy": "^2.0" "tightenco/ziggy": "^2.0"
}, },

871
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -95,6 +95,10 @@
'prefix_indexes' => true, 'prefix_indexes' => true,
'search_path' => 'public', 'search_path' => 'public',
'sslmode' => 'prefer', 'sslmode' => 'prefer',
'options' => [
'LC_COLLATE' => env('PGSQL_LC_COLLATE', 'en_US.UTF-8'),
'LC_CTYPE' => env('PGSQL_LC_CTYPE', 'en_US.UTF-8'),
]
], ],
'sqlsrv' => [ 'sqlsrv' => [

203
config/scout.php Normal file
View File

@ -0,0 +1,203 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Search Engine
|--------------------------------------------------------------------------
|
| This option controls the default search connection that gets used while
| using Laravel Scout. This connection is used when syncing all models
| to the search service. You should adjust this based on your needs.
|
| Supported: "algolia", "meilisearch", "typesense",
| "database", "collection", "null"
|
*/
'driver' => env('SCOUT_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Index Prefix
|--------------------------------------------------------------------------
|
| Here you may specify a prefix that will be applied to all search index
| names used by Scout. This prefix may be useful if you have multiple
| "tenants" or applications sharing the same search infrastructure.
|
*/
'prefix' => env('SCOUT_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Queue Data Syncing
|--------------------------------------------------------------------------
|
| This option allows you to control if the operations that sync your data
| with your search engines are queued. When this is set to "true" then
| all automatic data syncing will get queued for better performance.
|
*/
'queue' => env('SCOUT_QUEUE', true),
/*
|--------------------------------------------------------------------------
| Database Transactions
|--------------------------------------------------------------------------
|
| This configuration option determines if your data will only be synced
| with your search indexes after every open database transaction has
| been committed, thus preventing any discarded data from syncing.
|
*/
'after_commit' => false,
/*
|--------------------------------------------------------------------------
| Chunk Sizes
|--------------------------------------------------------------------------
|
| These options allow you to control the maximum chunk size when you are
| mass importing data into the search engine. This allows you to fine
| tune each of these chunk sizes based on the power of the servers.
|
*/
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
/*
|--------------------------------------------------------------------------
| Soft Deletes
|--------------------------------------------------------------------------
|
| This option allows to control whether to keep soft deleted records in
| the search indexes. Maintaining soft deleted records can be useful
| if your application still needs to search for the records later.
|
*/
'soft_delete' => true,
/*
|--------------------------------------------------------------------------
| Identify User
|--------------------------------------------------------------------------
|
| This option allows you to control whether to notify the search engine
| of the user performing the search. This is sometimes useful if the
| engine supports any analytics based on this application's users.
|
| Supported engines: "algolia"
|
*/
'identify' => env('SCOUT_IDENTIFY', false),
/*
|--------------------------------------------------------------------------
| Algolia Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
| search engine which works great with Scout out of the box. Just plug
| in your application ID and admin API key to get started searching.
|
*/
'algolia' => [
'id' => env('ALGOLIA_APP_ID', 'ZDAXR87LZV'),
'secret' => env('ALGOLIA_SECRET', '8797318d18e10541ad15d49ae1e64db2'),
],
/*
|--------------------------------------------------------------------------
| Meilisearch Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Meilisearch settings. Meilisearch is an open
| source search engine with minimal configuration. Below, you can state
| the host and key information for your own Meilisearch installation.
|
| See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options
|
*/
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
'index-settings' => [
// 'users' => [
// 'filterableAttributes'=> ['id', 'name', 'email'],
// ],
],
],
/*
|--------------------------------------------------------------------------
| Typesense Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Typesense settings. Typesense is an open
| source search engine using minimal configuration. Below, you will
| state the host, key, and schema configuration for the instance.
|
*/
'typesense' => [
'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
'nearest_node' => [
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
],
// 'max_total_results' => env('TYPESENSE_MAX_TOTAL_RESULTS', 1000),
'model-settings' => [
// User::class => [
// 'collection-schema' => [
// 'fields' => [
// [
// 'name' => 'id',
// 'type' => 'string',
// ],
// [
// 'name' => 'name',
// 'type' => 'string',
// ],
// [
// 'name' => 'created_at',
// 'type' => 'int64',
// ],
// ],
// 'default_sorting_field' => 'created_at',
// ],
// 'search-parameters' => [
// 'query_by' => 'name'
// ],
// ],
],
],
];

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'title' => fake()->sentence(),
'body' => fake()->paragraph(),
'slug' => fake()->slug(),
];
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->string('slug');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};

View File

@ -69,7 +69,7 @@ public function run(): void
'nu' => rand(100000,200000), 'nu' => rand(100000,200000),
'first_name' => '', 'first_name' => '',
'last_name' => '', 'last_name' => '',
'full_name' => 'test d.o.o.', 'full_name' => 'Naročnik d.o.o.',
'gender' => 'm', 'gender' => 'm',
'birthday' => '2000-01-01', 'birthday' => '2000-01-01',
'tax_number' => '22424345', 'tax_number' => '22424345',

View File

@ -0,0 +1,17 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class PostSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
\App\Models\Post::factory(500)->create();
}
}

View File

@ -1,5 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "CommonJS",
"target": "ES6",
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["resources/js/*"] "@/*": ["resources/js/*"]

115
package-lock.json generated
View File

@ -5,6 +5,11 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@vuepic/vue-datepicker": "^9.0.3", "@vuepic/vue-datepicker": "^9.0.3",
@ -12,11 +17,15 @@
"flowbite": "^2.5.2", "flowbite": "^2.5.2",
"flowbite-vue": "^0.1.6", "flowbite-vue": "^0.1.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-design-icons-iconfont": "^6.7.0",
"tailwindcss-inner-border": "^0.2.0", "tailwindcss-inner-border": "^0.2.0",
"vue3-apexcharts": "^1.7.0" "vue-search-input": "^1.1.16",
"vue3-apexcharts": "^1.7.0",
"vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@inertiajs/vue3": "^1.0.14", "@inertiajs/vue3": "^1.0.14",
"@mdi/js": "^7.4.47",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
@ -515,6 +524,73 @@
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz",
"integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz",
"integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
"license": "(CC-BY-4.0 AND MIT)",
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/vue-fontawesome": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz",
"integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==",
"license": "MIT",
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"vue": ">= 3.0.0 < 4"
}
},
"node_modules/@headlessui/vue": { "node_modules/@headlessui/vue": {
"version": "1.7.23", "version": "1.7.23",
"resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz",
@ -632,6 +708,13 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@mdi/js": {
"version": "7.4.47",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2335,6 +2418,12 @@
"@jridgewell/sourcemap-codec": "^1.5.0" "@jridgewell/sourcemap-codec": "^1.5.0"
} }
}, },
"node_modules/material-design-icons-iconfont": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz",
"integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA==",
"license": "Apache-2.0"
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -2974,6 +3063,12 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/sortablejs": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz",
"integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==",
"license": "MIT"
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -3466,6 +3561,12 @@
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/vue-search-input": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/vue-search-input/-/vue-search-input-1.1.16.tgz",
"integrity": "sha512-hMZJHN/HppamwF24vPVhWVy4334WEmChkuAEnmDuiR3SeKrAPjK5mYb0vKBiAWP4QL1p2d+JhvbUe444uDTEMw==",
"license": "MIT"
},
"node_modules/vue3-apexcharts": { "node_modules/vue3-apexcharts": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/vue3-apexcharts/-/vue3-apexcharts-1.7.0.tgz", "resolved": "https://registry.npmjs.org/vue3-apexcharts/-/vue3-apexcharts-1.7.0.tgz",
@ -3476,6 +3577,18 @@
"vue": "> 3.0.0" "vue": "> 3.0.0"
} }
}, },
"node_modules/vuedraggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz",
"integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
"license": "MIT",
"dependencies": {
"sortablejs": "1.14.0"
},
"peerDependencies": {
"vue": "^3.0.1"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -7,6 +7,7 @@
}, },
"devDependencies": { "devDependencies": {
"@inertiajs/vue3": "^1.0.14", "@inertiajs/vue3": "^1.0.14",
"@mdi/js": "^7.4.47",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
@ -19,6 +20,11 @@
"vue": "^3.3.13" "vue": "^3.3.13"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/vue-fontawesome": "^3.0.8",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5", "@heroicons/vue": "^2.1.5",
"@vuepic/vue-datepicker": "^9.0.3", "@vuepic/vue-datepicker": "^9.0.3",
@ -26,7 +32,10 @@
"flowbite": "^2.5.2", "flowbite": "^2.5.2",
"flowbite-vue": "^0.1.6", "flowbite-vue": "^0.1.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"material-design-icons-iconfont": "^6.7.0",
"tailwindcss-inner-border": "^0.2.0", "tailwindcss-inner-border": "^0.2.0",
"vue3-apexcharts": "^1.7.0" "vue-search-input": "^1.1.16",
"vue3-apexcharts": "^1.7.0",
"vuedraggable": "^4.1.0"
} }
} }

View File

@ -1,4 +1,5 @@
@import '/node_modules/floating-vue/dist/style.css'; @import '/node_modules/floating-vue/dist/style.css';
@import '/node_modules/vue-search-input/dist/styles.css';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View File

@ -0,0 +1,135 @@
<script setup>
import { FwbListGroup, FwbListGroupItem } from 'flowbite-vue';
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue';
import draggable from 'vuedraggable';
const props = defineProps({
list1: Array,
list2: Array
});
const item1 = {
id: 1,
name: 'Item 1'
}
const item2 = {
id: 2,
name: 'Item 2'
}
const item3 = {
id: 3,
name: 'Item 3'
}
const isDragging = ref(false);
let containerOne = ref([item1, item2])
let containerTwo = ref([item3])
const dragOptions = computed(() => {
return {
animation: 200,
group: "actions",
disabled: false,
ghostClass: "ghost"
}
});
const removeAt = (idx, targeContainer) => {
targeContainer.splice(idx, 1);
};
watch(
() => containerOne.value,
(value) => {
console.log(value)
},
{ deep: true }
);
</script>
<template>
<div class="grid grid-cols-4 gap-4">
<draggable
class="list-group"
:list="containerOne"
item-key="id"
:component-data="{
tag: 'ul',
type: 'transition-group',
name: !isDragging ? 'flip-list' : null
}"
v-bind="dragOptions"
@start="isDragging = true"
@end="isDragging = false"
group="actions"
>
<template #item="{element, index}">
<fwb-list-group-item class="flex justify-between">
<span class="text">{{ element.name }} </span>
<i class=" cursor-pointer" @click="removeAt(index, containerOne)"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/>
</svg></i>
</fwb-list-group-item>
</template>
</draggable>
<draggable
class="list-group"
:list="containerTwo"
:component-data="{
tag: 'ul',
type: 'transition-group',
name: !isDragging ? 'flip-list' : null
}"
item-key="id"
v-bind="dragOptions"
@start="isDragging = true"
@end="isDragging = false"
group="actions"
>
<template #item="{element, index}">
<fwb-list-group-item class="flex justify-between">
<span class="text">{{ element.name }} </span>
<i class="cursor-pointer" @click="removeAt(index, containerOne)"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/>
</svg></i>
</fwb-list-group-item>
</template>
</draggable>
</div>
</template>
<style lang="css">
.button {
margin-top: 35px;
}
.flip-list-move {
transition: transform 0.5s;
}
.no-move {
transition: transform 0s;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
.list-group {
min-height: 20px;
}
.list-group-item {
cursor: move;
}
.list-group-item i {
cursor: pointer;
}
</style>

View File

@ -0,0 +1,79 @@
<script setup>
import { ref } from 'vue';
const item1 = {
id: 1,
text: 'Item 1'
};
const item2 = {
id: 2,
text: 'Item 2'
};
let containerOne = ref([item1, item2])
let containerTwo = ref([])
const handleDragStart = (event, container, itemData) => {
event.dataTransfer.setData('application/json', JSON.stringify(itemData));
};
const handleDrop = (event, targetContainer) => {
const itemData = JSON.parse(event.dataTransfer.getData('application/json'));
if (targetContainer === containerOne.value) {
containerTwo.value = containerTwo.value.filter(i => i.id !== itemData.id);
} else if (targetContainer === containerTwo.value) {
containerOne.value = containerOne.value.filter(i => i.id !== itemData.id);
}
targetContainer.push(itemData);
};
</script>
<template>
<div class="drag-drop-container">
<div class="container-one"
v-on:dragover.prevent
v-on:drop="handleDrop($event, containerOne)">
<div class="item"
v-for="item in containerOne"
:key="item.id"
draggable="true"
v-on:dragstart="handleDragStart($event, containerOne, item)">
{{ item.text }}
</div>
</div>
<div class="container-two"
v-on:dragover.prevent
v-on:drop="handleDrop($event, containerTwo)">
<div class="item"
v-for="item in containerTwo"
:key="item.id"
draggable="true"
v-on:dragstart="handleDragStart($event, containerTwo, item)">
{{ item.text }}
</div>
</div>
</div>
</template>
<style scoped>
.drag-drop-container {
display: flex;
}
.container-one, .container-two {
border: 1px solid #1a1a1a;
width: 600px;
height: 800px;
padding: 10px;
}
.item {
padding: 10px;
background-color: black;
color: white;
cursor: pointer;
width: 90%;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
import { Head, Link, router, usePage } from '@inertiajs/vue3'; import { Head, Link, router, usePage } from '@inertiajs/vue3';
import ApplicationMark from '@/Components/ApplicationMark.vue'; import ApplicationMark from '@/Components/ApplicationMark.vue';
import Banner from '@/Components/Banner.vue'; import Banner from '@/Components/Banner.vue';
@ -8,16 +8,66 @@ import DropdownLink from '@/Components/DropdownLink.vue';
import NavLink from '@/Components/NavLink.vue'; import NavLink from '@/Components/NavLink.vue';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink.vue'; import ResponsiveNavLink from '@/Components/ResponsiveNavLink.vue';
import Breadcrumbs from '@/Components/Breadcrumbs.vue'; import Breadcrumbs from '@/Components/Breadcrumbs.vue';
import axios from 'axios';
import { debounce } from 'lodash';
import { FwbButton, FwbInput, FwbListGroup, FwbListGroupItem } from 'flowbite-vue';
import { AdjustmentIcon, SearchIcon } from '@/Utilities/Icons';
const props = defineProps({ const props = defineProps({
title: String, title: String,
}); });
const showingNavigationDropdown = ref(false); const showingNavigationDropdown = ref(false);
const querySearchDiv = ref(null);
const querySearch = ref('');
const querySearchList = ref(true);
const querySearchResult = ref([]);
const logout = () => { const logout = () => {
router.post(route('logout')); router.post(route('logout'));
}; };
const setRoute = (index) => {
return route('client.show', index);
}
const onSearchFocus = () => {
if(querySearch.value.length > 0) {
querySearchList.value = false;
}
}
const onSearchBlue = () => {
setTimeout(() => querySearchList.value = true, 100)
}
watch(querySearch, debounce((value) => {
axios.get(
route('search'),
{
params: {
query: value,
limit: 5,
tag: ''
}
}
)
.then(function(res) {
querySearchResult.value = res.data
querySearchList.value = false;
console.log(res);
})
.catch(function(error){
console.log(error)
})
.finally(function(){
});
}, 300));
</script> </script>
<template> <template>
@ -65,6 +115,42 @@ const logout = () => {
<div class="hidden sm:flex sm:items-center sm:ms-6"> <div class="hidden sm:flex sm:items-center sm:ms-6">
<!-- Settings Dropdown --> <!-- Settings Dropdown -->
<div>
<fwb-input
v-model="querySearch"
placeholder="Iskalnik..."
size="md"
@focus="onSearchFocus"
@blur="onSearchBlue"
>
<template #prefix>
<SearchIcon />
</template>
</fwb-input>
<fwb-list-group ref="querySearchDiv" class="absolute" :hidden="querySearchList">
<fwb-list-group-item>
<div>
<p>Naročniki:</p>
<fwb-list-group>
<fwb-list-group-item hover v-for="client in querySearchResult.clients">
<a :href="route('client.show', {uuid: client.uuid})">{{ client.person.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</div>
</fwb-list-group-item>
<fwb-list-group-item>
<div>
<p>Primeri:</p>
<fwb-list-group>
<fwb-list-group-item hover v-for="clientCase in querySearchResult.client_cases">
<a :href="route('clientCase.show', {uuid: clientCase.uuid})">{{ clientCase.person.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</div>
</fwb-list-group-item>
</fwb-list-group>
</div>
<div class="ms-3 relative"> <div class="ms-3 relative">
<Dropdown align="right" width="48"> <Dropdown align="right" width="48">
<template #trigger> <template #trigger>
@ -154,7 +240,6 @@ const logout = () => {
Nastavitve Nastavitve
</ResponsiveNavLink> </ResponsiveNavLink>
</div> </div>
<!-- Responsive Settings Options --> <!-- Responsive Settings Options -->
<div class="pt-4 pb-1 border-t border-gray-200"> <div class="pt-4 pb-1 border-t border-gray-200">
<div class="flex items-center px-4"> <div class="flex items-center px-4">
@ -233,11 +318,12 @@ const logout = () => {
</nav> </nav>
<!-- Page Heading --> <!-- Page Heading -->
<header v-if="$slots.header" class="bg-white shadow"> <header v-if="$slots.header" class="bg-white shadow ">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<Breadcrumbs :breadcrumbs="$page.props.breadcrumbs"></Breadcrumbs> <Breadcrumbs :breadcrumbs="$page.props.breadcrumbs"></Breadcrumbs>
</div> </div>
</header> </header>
<!-- Page Content --> <!-- Page Content -->

View File

@ -96,7 +96,7 @@ const closeDrawer = () => {
</div> </div>
</div> </div>
</div> </div>
<div class="pt-1"> <!--div class="pt-1">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4 border-blue-400"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4 border-blue-400">
<div class="mx-auto max-w-4x1 p-3"> <div class="mx-auto max-w-4x1 p-3">
@ -104,8 +104,8 @@ const closeDrawer = () => {
</div> </div>
</div> </div>
</div> </div>
</div> </div-->
<div class="pt-12"> <div class="pt-6">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4 border-red-400"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg border-l-4 border-red-400">
<div class="mx-auto max-w-4x1 p-3"> <div class="mx-auto max-w-4x1 p-3">

View File

@ -19,7 +19,6 @@ const activeTab = ref('actions')
<div class="pt-12"> <div class="pt-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg"> <div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<fwb-tabs v-model="activeTab" variant="underline" > <fwb-tabs v-model="activeTab" variant="underline" >
<fwb-tab name="actions" title="Actions"> <fwb-tab name="actions" title="Actions">
<ActionTable :actions="actions" /> <ActionTable :actions="actions" />
@ -28,7 +27,6 @@ const activeTab = ref('actions')
<DecisionTable :decisions="decisions" /> <DecisionTable :decisions="decisions" />
</fwb-tab> </fwb-tab>
</fwb-tabs> </fwb-tabs>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,36 +1,36 @@
<script setup> <script setup>
import BasicTable from '@/Components/BasicTable.vue'; import { FwbTable, FwbTableBody, FwbTableHead, FwbTableHeadCell, FwbTableCell, FwbTableRow } from 'flowbite-vue';
import { LinkOptions as C_LINK, TableColumn as C_TD, TableRow as C_TR} from '@/Shared/AppObjects'; import { EditIcon, TrashBinIcon } from '@/Utilities/Icons';
const props = defineProps({ const props = defineProps({
actions: Array actions: Array
}); });
const header = [
C_TD.make('#', 'header'),
C_TD.make('Name', 'header'),
C_TD.make('Color tag', 'header'),
C_TD.make('Decisions', 'header')
];
const createBody = (data) => {
let content = [];
data.forEach((a) => {
const cols = [
C_TD.make(a.id, 'body'),
C_TD.make(a.name, 'body'),
C_TD.make(a.color_tag, 'body'),
C_TD.make(a.decisions.length, 'body')
];
content.push(C_TR.make(cols));
});
return content;
}
</script> </script>
<template> <template>
<BasicTable :header="header" :body="createBody(actions)" /> <fwb-table>
<fwb-table-head>
<fwb-table-head-cell>#</fwb-table-head-cell>
<fwb-table-head-cell>Name</fwb-table-head-cell>
<fwb-table-head-cell>Color tag</fwb-table-head-cell>
<fwb-table-head-cell>Decisions</fwb-table-head-cell>
<fwb-table-head-cell>
<span class="sr-only">Edit</span>
</fwb-table-head-cell>
</fwb-table-head>
<fwb-table-body>
<fwb-table-row v-for="act in actions" :key="act.id">
<fwb-table-cell>{{ act.id }}</fwb-table-cell>
<fwb-table-cell>{{ act.name }}</fwb-table-cell>
<fwb-table-cell>{{ act.color_tag }}</fwb-table-cell>
<fwb-table-cell>{{ act.decisions.length }}</fwb-table-cell>
<fwb-table-cell>
<button><EditIcon /></button>
<button><TrashBinIcon /></button>
</fwb-table-cell>
</fwb-table-row>
</fwb-table-body>
</fwb-table>
</template> </template>

View File

@ -0,0 +1,19 @@
<script setup>
import DragDropListEx from '@/Components/DragDropListEx.vue';
import AppLayout from '@/Layouts/AppLayout.vue';
</script>
<template>
<AppLayout title="Testing">
<div class="pt-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<div class="grid grid-flow-col">
<DragDropListEx />
</div>
</div>
</div>
</div>
</AppLayout>
</template>

View File

@ -0,0 +1,66 @@
import { ref } from "vue";
const Icon = {
props: {
css: {
type: String,
default: 'text-gray-800'
}
}
}
const AddressBookIcon = {
__proto__: Icon,
setup: () => {
console.log(this.props)
},
template: `<svg class="w-6 h-6 dark:text-white" :class="css" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 6H5m2 3H5m2 3H5m2 3H5m2 3H5m11-1a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2M7 3h11a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm8 7a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"/>
</svg>`
};
const AlignCenterIcon = {
__proto__: Icon,
template: `<svg class="w-6 h-6 dark:text-white" :class="css" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 6h8M6 10h12M8 14h8M6 18h12"/>
</svg>`
}
const EditIcon = {
__proto__: Icon,
template: `<svg class="w-6 h-6 dark:text-white" :class="css" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
</svg>`
}
const TrashBinIcon = {
__proto__: Icon,
template: `<svg class="w-6 h-6 dark:text-white" :class="css" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z" clip-rule="evenodd"/>
</svg>`
}
const SearchIcon = {
__proto__: Icon,
template: `<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
</svg>`
}
const AdjustmentIcon = {
__proto__: Icon,
template: `<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M20 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6h-2m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4"/>
</svg>
`
}
export {
AddressBookIcon,
AlignCenterIcon,
EditIcon,
TrashBinIcon,
SearchIcon,
AdjustmentIcon
};

View File

@ -1,7 +1,7 @@
import './bootstrap'; import './bootstrap';
import '../css/app.css'; import '../css/app.css';
import { createApp, h } from 'vue'; import { createApp, h } from 'vue/dist/vue.esm-bundler.js';
import { createInertiaApp } from '@inertiajs/vue3'; import { createInertiaApp } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy'; import { ZiggyVue } from '../../vendor/tightenco/ziggy';

View File

@ -11,4 +11,12 @@
Route::get('/person', function(){ Route::get('/person', function(){
return new PersonCollection(Person::all()); return new PersonCollection(Person::all());
})->middleware('auth:sanctum');
Route::get('/search', function(Request $request){
$query = '41242523';
$persons = App\Models\Person\Person::search($query)->get();
return $persons;
})->middleware('auth:sanctum'); })->middleware('auth:sanctum');

View File

@ -6,6 +6,7 @@
use App\Http\Controllers\ContractController; use App\Http\Controllers\ContractController;
use App\Http\Controllers\SettingController; use App\Http\Controllers\SettingController;
use App\Models\Person\Person; use App\Models\Person\Person;
use Illuminate\Http\Request;
use ArielMejiaDev\LarapexCharts\LarapexChart; use ArielMejiaDev\LarapexCharts\LarapexChart;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Inertia\Inertia; use Inertia\Inertia;
@ -36,6 +37,35 @@
); );
})->name('dashboard'); })->name('dashboard');
Route::get('testing', function() {
return Inertia::render('Testing', []);
});
Route::get('search', function(Request $request) {
if( !empty($request->input('query')) ) {
$clients = App\Models\Client::search($request->input('query'))
->query(function($q) use($request) {
$q->with('person')->limit($request->input('limit'));
})
->get();
$clientCases = App\Models\ClientCase::search($request->input('query'))
->query(function($q) use($request) {
$q->with('person')->limit($request->input('limit'));
})
->get();
return [
'clients' => $clients,
'client_cases' => $clientCases
];
}
return [];
})->name('search');
//client //client
Route::get('clients', [ClientController::class, 'index'])->name('client'); Route::get('clients', [ClientController::class, 'index'])->name('client');
Route::get('clients/{client:uuid}', [ClientController::class, 'show'])->name('client.show'); Route::get('clients/{client:uuid}', [ClientController::class, 'show'])->name('client.show');

View File

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
class AlgoliaSearchTest extends TestCase
{
/**
* A basic feature test example.
*/
public function test_example(): void
{
$client = \Algolia\AlgoliaSearch\SearchClient::create('ZDAXR87LZV','8797318d18e10541ad15d49ae1e64db2');
$index = $client->initIndex('myposts_index');
$index->saveObject([
'objectID' => 1,
'name' => 'Test record'
]);
//$response->assertStatus(200);
}
}