first change

This commit is contained in:
2025-07-27 17:40:56 +05:45
commit f8b9a6725b
3152 changed files with 229528 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
<?php
namespace Modules\Client\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Modules\Client\Interfaces\ClientInterface;
use Modules\Client\Models\Client;
use Yajra\DataTables\Facades\DataTables;
class ClientController extends Controller
{
private $client;
public function __construct(ClientInterface $client)
{
$this->client = $client;
}
/**
* Display a listing of the resource.
*/
public function index()
{
if (request()->ajax()) {
$model = Client::query()->orderBy('order');
return DataTables::eloquent($model)
->addIndexColumn()
->setRowClass('tableRow')
->editColumn('logo', function (Client $client) {
return $client->getRawOriginal('logo') ? "<img src='{$client->logo}' alt='{$client->name}' class='rounded avatar-sm material-shadow ms-2 img-thumbnail'>" : '-';
})
->editColumn('title', function (Client $client) {
$route = route('client.show', $client->id);
$html ="<a class='link link-primary' href='{$route}'>{{ $client->name }}</a>";
return $html;
})
->editColumn('manager_name', function (Client $client) {
return $client->manager_name ?? "-";
})
->editColumn('status', function (Client $client) {
$status = $client->status ? 'Published' : 'Draft';
$color = $client->status ? 'text-success' : 'text-danger';
return "<p class='{$color}'>{$status}</p>";
})
->addColumn('action', 'client::client.datatable.action')
->rawColumns(['status', 'logo', 'action'])
->toJson();
}
return view('client::client.index', [
'title' => 'Client List',
]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$data['title'] = 'Create Client';
$data['editable'] = false;
return view('client::client.create', $data);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$client = null;
$validated = $request->validate([
"name" => ["required", "string", "max:255"],
]);
$request->merge([
"company_name" => $request->name,
'order' => ($maxOrder = Client::max('order')) !== null ? ++$maxOrder : 1,
]);
try {
DB::transaction(function () use ($request, &$client) {
$input = $request->only(Client::getFillableFields());
$client = Client::create($input);
});
flash()->success("Client {$client->name} has been created");
return redirect()->route('client.index');
} catch (\Exception $e) {
flash()->error($e->getMessage());
return redirect()->back()->withInput();
}
}
/**
* Show the specified resource.
*/
public function show($id)
{
$data["title"] = "Show Client";
$data["client"] = $this->client->findById($id);
return view('client::client.show', $data);
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
$data['title'] = 'Edit Client';
$data['editable'] = true;
$data["client"] = $this->client->findById($id);
return view('client::client.edit', $data);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id)
{
$validated = $request->validate([
"title" => ["required"],
]);
try {
DB::transaction(function () use ($request, $id) {
$input = $request->only(Client::getFillableFields());
$client = $this->client->update($id, $input);
});
flash()->success('Client has been updated!');
return redirect()->route('client.index');
} catch (\Throwable $th) {
flash()->error($th->getMessage());
return redirect()->back()->withInput();
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy($id)
{
try {
DB::transaction(function () use ($id) {
$client = $this->client->delete($id);
});
return response()->json([
'status' => 200,
'message' => 'Client has been deleted!',
], 200);
} catch (\Throwable $th) {
return response()->json([
'status' => 500,
'message' => 'Failed to delete client!',
'error' => $th->getMessage(),
], 500);
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Modules\Client\Interfaces;
use App\Interfaces\ModelInterface;
interface ClientInterface extends ModelInterface
{
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Modules\Client\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\Client\Database\Factories\ClientFactory;
use Modules\Product\Models\Product;
use Modules\User\Traits\HasActivityLogs;
class Client extends Model
{
use HasFactory, HasActivityLogs;
/**
* The attributes that are mass assignable.
*/
protected $fillable = [
'name',
'company_name',
'contact',
'logo',
'manager_name',
'manager_contact',
'poc_name',
'poc_contact',
'promised_document',
'poc_document',
'description',
'status',
'order',
'createdby',
'updatedby',
];
public function products()
{
return $this->hasMany(Product::class, 'client_id');
}
protected static function newFactory(): ClientFactory
{
return ClientFactory::new();
}
}

View File

View File

@@ -0,0 +1,138 @@
<?php
namespace Modules\Client\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Modules\Client\Interfaces\ClientInterface;
use Modules\Client\Repositories\ClientRepository;
use Nwidart\Modules\Traits\PathNamespace;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class ClientServiceProvider extends ServiceProvider
{
use PathNamespace;
protected string $name = 'Client';
protected string $nameLower = 'client';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->name, 'database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->bind(ClientInterface::class, ClientRepository::class);
$this->app->register(EventServiceProvider::class);
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->nameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->nameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->name, 'lang'), $this->nameLower);
$this->loadJsonTranslationsFrom(module_path($this->name, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$relativeConfigPath = config('modules.paths.generator.config.path');
$configPath = module_path($this->name, $relativeConfigPath);
if (is_dir($configPath)) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath));
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$relativePath = str_replace($configPath . DIRECTORY_SEPARATOR, '', $file->getPathname());
$configKey = $this->nameLower . '.' . str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $relativePath);
$key = ($relativePath === 'config.php') ? $this->nameLower : $configKey;
$this->publishes([$file->getPathname() => config_path($relativePath)], 'config');
$this->mergeConfigFrom($file->getPathname(), $key);
}
}
}
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->nameLower);
$sourcePath = module_path($this->name, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower);
$componentNamespace = $this->module_namespace($this->name, $this->app_path(config('modules.paths.generator.component-class.path')));
Blade::componentNamespace($componentNamespace, $this->nameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->nameLower)) {
$paths[] = $path.'/modules/'.$this->nameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Modules\Client\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event handler mappings for the application.
*
* @var array<string, array<int, string>>
*/
protected $listen = [];
/**
* Indicates if events should be discovered.
*
* @var bool
*/
protected static $shouldDiscoverEvents = true;
/**
* Configure the proper event listeners for email verification.
*/
protected function configureEmailVerification(): void
{
//
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Modules\Client\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
protected string $name = 'Client';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*/
protected function mapWebRoutes(): void
{
Route::middleware('web')->group(module_path($this->name, '/routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php'));
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Modules\Client\Repositories;
use Illuminate\Http\Request;
use Modules\Client\Interfaces\ClientInterface;
use Modules\Client\Models\Client;
class ClientRepository implements ClientInterface
{
public function findAll($request, callable $query = null, bool $paginate = false, int $limit = 10)
{
$baseQuery = Client::query();
if ($request->filled('search')) {
$baseQuery->whereAny(
[
'name',
'manager_name',
'poc_name',
],
'LIKE',
"%{$request->search}%"
);
}
if ($query) {
$query($baseQuery);
}
if ($paginate) {
return $baseQuery->paginate($limit);
}
return $baseQuery->get();
}
public function findById($id, callable $query = null)
{
$baseQuery = Client::query();
if (is_callable($query)) {
$query($baseQuery);
}
return $baseQuery->where('id', $id)->firstOrFail();
}
public function delete($id)
{
$client = $this->findById($id);
$client->delete();
return $client;
}
public function create(array $data)
{
$client = Client::create($data);
return $client;
}
public function update($id, array $data)
{
$client = $this->findById($id);
$client->update($data);
return $client;
}
public function pluck(callable $query = null)
{
$baseQuery = Client::query();
if (is_callable($query)) {
$query($baseQuery);
}
return $baseQuery->pluck('name', 'id');
}
}

View File

View File

@@ -0,0 +1,30 @@
{
"name": "nwidart/client",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Client\\": "app/",
"Modules\\Client\\Database\\Factories\\": "database/factories/",
"Modules\\Client\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\Client\\Tests\\": "tests/"
}
}
}

View File

View File

@@ -0,0 +1,5 @@
<?php
return [
'name' => 'Client',
];

View File

@@ -0,0 +1,37 @@
<?php
namespace Modules\Client\Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class ClientFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*/
protected $model = \Modules\Client\Models\Client::class;
/**
* Define the model's default state.
*/
public function definition(): array
{
return [
'name' => $this->faker->name,
'company_name' => $this->faker->company,
'contact' => $this->faker->phoneNumber,
'logo' => $this->faker->imageUrl(200, 200, 'business', true, 'Faker'),
'manager_name' => $this->faker->name,
'manager_contact' => $this->faker->phoneNumber,
'poc_name' => $this->faker->name,
'poc_contact' => $this->faker->phoneNumber,
'promised_document' => $this->faker->word . '.pdf',
'poc_document' => $this->faker->word . '.pdf',
'description' => $this->faker->paragraph,
'status' => $this->faker->numberBetween(0, 1),
'createdby' => 1,
'updatedby' => 1,
];
}
}

View File

@@ -0,0 +1,46 @@
<?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('clients', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->string('company_name')->nullable();
$table->string('contact')->nullable();
$table->string('logo')->nullable();
$table->string('manager_name')->nullable();
$table->string('manager_contact')->nullable();
$table->string('poc_name')->nullable();
$table->string('poc_contact')->nullable();
$table->string('promised_document')->nullable();
$table->string('poc_document')->nullable();
$table->longtext('description')->nullable();
$table->integer('status')->default(0);
$table->unsignedBigInteger('order')->nullable();
$table->unsignedBigInteger('createdby')->nullable();
$table->unsignedBigInteger('updatedby')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('clients');
}
};

View File

View File

@@ -0,0 +1,19 @@
<?php
namespace Modules\Client\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Client\Database\Factories\ClientFactory;
use Modules\Client\Models\Client;
class ClientDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
Client::factory(10)->create();
// $this->call([]);
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "Client",
"alias": "client",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Client\\Providers\\ClientServiceProvider"
],
"files": []
}

View File

@@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}

View File

View File

@@ -0,0 +1,14 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb />
{{ html()->form('POST')->route('client.store')->class(['needs-validation'])->attributes(['novalidate', 'enctype' => 'multipart/form-data'])->open() }}
@include('client::client.partials.form')
{{ html()->form()->close() }}
</div>
@endsection

View File

@@ -0,0 +1,12 @@
<div class="hstack flex-wrap gap-3">
<a href="{{ route('client.edit', $id) }}" data-bs-toggle="tooltip"
data-bs-placement="bottom" data-bs-title="Edit" class="link-success fs-15 edit-item-btn"><i
class=" ri-edit-2-line"></i></a>
<a data-link="{{ route('client.toggle', $id) }}" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Toggle" data-status="{{ $status == 1 ? 'Draft' : 'Published' }}"
class="link-info fs-15 toggle-item"><i class="{{ $status == 1 ? 'ri-toggle-line' : 'ri-toggle-fill' }}"></i></a>
<a href="javascript:void(0);" data-link="{{ route('client.destroy', $id) }}" class="link-danger fs-15 remove-item"
data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Delete"><i class="ri-delete-bin-6-line"></i>
</a>
</div>

View File

@@ -0,0 +1,15 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb />
{{ html()->modelForm($client, 'PUT')->route('client.update', $client->id)->class(['needs-validation'])->attributes(['novalidate', 'enctype' => 'multipart/form-data'])->open() }}
@include('client::client.partials.form')
{{ html()->closeModelForm() }}
</div>
@endsection

View File

@@ -0,0 +1,47 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb :title="$title" />
@if ($errors->any())
<x-flash-message type="danger" :messages="$errors->all()" />
@endif
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-header d-flex align-items-center justify-content-between">
<h5 class="card-title mb-0">{{ $title }}</h5>
<a href="{{ route('client.create') }}" class="btn btn-primary waves-effect waves-light text-white"><i class="ri-add-line align-middle"></i> Create</a>
</div>
<div class="card-body">
@php
$columns = [
[
'title' => 'S.N',
'data' => 'DT_RowIndex',
'name' => 'DT_RowIndex',
'orderable' => false,
'searchable' => false,
'sortable' => false,
],
['title' => 'Logo', 'data' => 'logo', 'name' => 'logo'],
['title' => 'Name', 'data' => 'name', 'name' => 'name'],
['title' => 'Contact', 'data' => 'contact', 'name' => 'contact'],
['title' => 'Manager', 'data' => 'manager_name', 'name' => 'manager_name'],
['title' => 'Status', 'data' => 'status', 'name' => 'status'],
[
'title' => 'Action',
'data' => 'action',
'orderable' => false,
'searchable' => false,
],
];
@endphp
<x-data-table-script :route="route('client.index')" :reorder="route('client.reorder')" :columns="$columns" />
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,106 @@
<div class="row">
<div class="col-md-8 col-xl-9">
<div class="card shadow-sm">
<div class="card-body">
<h6 class="card-title text-primary mb-4">Client Information</h6>
<div class="row g-3">
<div class="col-md-6">
{{ html()->label('Name')->class('form-label')->for('name') }}
{{ html()->span('*')->class('text-danger') }}
{{ html()->text('name')->class('form-control')->placeholder('Client Name')->required() }}
</div>
<div class="col-md-6">
{{ html()->label('Contact')->class('form-label')->for('contact') }}
{{ html()->span('*')->class('text-danger') }}
{{ html()->text('contact')->class('form-control')->placeholder('Contact Number')->required() }}
</div>
</div>
<div class="border border-1 border-dashed my-3"></div>
<h6 class="card-title text-primary mb-4">Manager Information</h6>
<div class="row g-3">
<div class="col-md-6">
{{ html()->label('Name')->class('form-label')->for('manager_name') }}
{{ html()->text('manager_name')->class('form-control')->placeholder('Manager Name') }}
</div>
<div class="col-md-6">
{{ html()->label('Contact')->class('form-label')->for('manager_contact') }}
{{ html()->text('manager_contact')->class('form-control')->placeholder('Manager Contact') }}
</div>
</div>
<div class="border border-1 border-dashed my-3"></div>
<h6 class="card-title text-primary mb-4">Point of Contact Information</h6>
<div class="row g-3">
<div class="col-md-6">
{{ html()->label('Name')->class('form-label')->for('poc_name') }}
{{ html()->text('poc_name')->class('form-control')->placeholder('Point of Contact Name') }}
</div>
<div class="col-md-6">
{{ html()->label('Number')->class('form-label')->for('poc_contact') }}
{{ html()->text('poc_contact')->class('form-control')->placeholder('Point of Contact Number') }}
</div>
</div>
<div class="border border-1 border-dashed my-3"></div>
<h6 class="card-title text-primary mb-4">Additional Information</h6>
<div class="row g-3">
<div class="col-md-12">
{{ html()->label('Description')->class('form-label')->for('description') }}
{{ html()->textarea('description')->class('form-control ckeditor-classic') }}
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 col-xl-3">
<div class="card">
<div class="card-header">
<h6 class="card-title mb-0 fs-14">
Published
</h6>
</div>
<div class="card-body">
{{ html()->label('Status')->class('form-label visually-hidden')->for('status') }}
{{ html()->select('status', config('constants.page_status_options'))->class('form-select choices-select') }}
</div>
<x-form-buttons :href="route('client.index')" :label="isset($client) ? 'Update' : 'Create'" />
</div>
<div class="card mb-3">
<div class="card-header">
<h5 class="card-title mb-0">Logo</h5>
</div>
<div class="card-body">
<div class="mb-3">
{{ html()->label('Logo')->class('form-label visually-hidden')->for('logo') }}
<x-image-input :data="$editable ? $client->getRawOriginal('logo') : null" id="logo" name="logo" :editable="$editable" :multiple=false />
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h5 class="card-title mb-0">Document</h5>
</div>
<div class="card-body">
<div class="mb-3">
{{ html()->label('Promised')->class('form-label')->for('promised_document') }}
<x-image-input :data="$editable ? $client->getRawOriginal('promised_document') : null" id="promised_document" name="promised_document" :editable="$editable"
:multiple=false />
</div>
<div class="mb-3">
{{ html()->label('POC')->class('form-label')->for('poc_document') }}
<x-image-input :data="$editable ? $client->getRawOriginal('poc_document') : null" id="poc_document" name="poc_document" :editable="$editable"
:multiple=false />
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,392 @@
@extends('layouts.app')
@section('content')
<div class="container-fluid">
<x-dashboard.breadcumb :title="$title" />
<div class="row">
<div class="col-lg-4 col-xxl-3">
<div class="card" id="contact-view-detail">
<div class="card-body text-center">
@if ($client->logo)
<div class="position-relative d-inline-block">
<img src="{{ $client->logo }}" alt="" class="avatar-lg rounded-circle img-thumbnail">
<span class="contact-active position-absolute rounded-circle bg-success"><span
class="visually-hidden"></span>
</span>
</div>
@endif
<h5 class="mt-4 mb-2">{{ $client->name }}</h5>
<ul class="list-inline mb-0">
<li class="list-inline-item avatar-xs">
<a href="tel:{{ $client->contact }}"
class="avatar-title bg-success-subtle text-success fs-15 rounded">
<i class="ri-phone-line"></i>
</a>
</li>
<li class="list-inline-item avatar-xs">
<a href="https://wa.me/{{ $client->whatsapp ?? $client->contact }}" target="_blank"
class="avatar-title bg-danger-subtle text-danger fs-15 rounded">
<i class="ri-whatsapp-line"></i>
</a>
</li>
</ul>
</div>
<div class="card-body">
<h6 class="text-muted text-uppercase fw-semibold mb-3">Client Information</h6>
<p class="text-muted mb-4">{!! strip_tags($client->description) !!}</p>
<div class="table-responsive table-card">
<table class="table table-borderless mb-0">
<tbody>
<tr>
<td class="fw-medium" scope="row">Contact</td>
<td>{{ $client->contact }}</td>
</tr>
<tr>
<td class="fw-medium" scope="row">Manager</td>
<td>{{ $client->manager_name ?? 'Not Provided' }}</td>
</tr>
<tr>
<td class="fw-medium" scope="row">Manager Contact</td>
<td>{{ $client->manager_contact ?? 'Not Provided' }}</td>
</tr>
<tr>
<td class="fw-medium" scope="row">POC Name</td>
<td>{{ $client->poc_name ?? 'Not Provided' }}</td>
</tr>
<tr>
<td class="fw-medium" scope="row">POC Contact</td>
<td>{{ $client->poc_contact ?? 'Not Provided' }}</td>
</tr>
@php
$status = $client->status ? 'Published' : 'Draft';
$color = $client->status ? 'success' : 'danger';
@endphp
<tr>
<td class="fw-medium" scope="row">Status</td>
<td>
<span
class="badge bg-{{ $color }}-subtle text-{{ $color }}">{{ $status }}</span>
</td>
</tr>
<tr>
<td class="fw-medium" scope="row">Created At</td>
<td>{{ getFormatted(date: $client->created_at) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{{-- <div class="card mb-3">
<div class="card-body">
<div class="d-flex mb-3">
<h6 class="card-title mb-0 flex-grow-1">Assigned To</h6>
<div class="flex-shrink-0">
<button type="button" class="btn btn-soft-danger btn-sm" data-bs-toggle="modal"
data-bs-target="#inviteMembersModal"><i class="ri-share-line me-1 align-bottom"></i>
Assigned Member</button>
</div>
</div>
<ul class="list-unstyled vstack gap-3 mb-0">
<li>
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<img src="assets/images/users/avatar-10.jpg" alt=""
class="avatar-xs rounded-circle">
</div>
<div class="flex-grow-1 ms-2">
<h6 class="mb-1"><a href="pages-profile.html">Tonya Noble</a></h6>
<p class="text-muted mb-0">Full Stack Developer</p>
</div>
<div class="flex-shrink-0">
<div class="dropdown">
<button class="btn btn-icon btn-sm fs-16 text-muted dropdown" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="ri-more-fill"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-eye-fill text-muted me-2 align-bottom"></i>View</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-star-fill text-muted me-2 align-bottom"></i>Favorite</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-delete-bin-5-fill text-muted me-2 align-bottom"></i>Delete</a>
</li>
</ul>
</div>
</div>
</div>
</li>
<li>
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<img src="assets/images/users/avatar-8.jpg" alt=""
class="avatar-xs rounded-circle">
</div>
<div class="flex-grow-1 ms-2">
<h6 class="mb-1"><a href="pages-profile.html">Thomas Taylor</a></h6>
<p class="text-muted mb-0">UI/UX Designer</p>
</div>
<div class="flex-shrink-0">
<div class="dropdown">
<button class="btn btn-icon btn-sm fs-16 text-muted dropdown" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="ri-more-fill"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-eye-fill text-muted me-2 align-bottom"></i>View</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-star-fill text-muted me-2 align-bottom"></i>Favorite</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-delete-bin-5-fill text-muted me-2 align-bottom"></i>Delete</a>
</li>
</ul>
</div>
</div>
</div>
</li>
<li>
<div class="d-flex align-items-center">
<div class="flex-shrink-0">
<img src="assets/images/users/avatar-2.jpg" alt=""
class="avatar-xs rounded-circle">
</div>
<div class="flex-grow-1 ms-2">
<h6 class="mb-1"><a href="pages-profile.html">Nancy Martino</a></h6>
<p class="text-muted mb-0">Web Designer</p>
</div>
<div class="flex-shrink-0">
<div class="dropdown">
<button class="btn btn-icon btn-sm fs-16 text-muted dropdown" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="ri-more-fill"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-eye-fill text-muted me-2 align-bottom"></i>View</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-star-fill text-muted me-2 align-bottom"></i>Favorite</a>
</li>
<li><a class="dropdown-item" href="javascript:void(0);"><i
class="ri-delete-bin-5-fill text-muted me-2 align-bottom"></i>Delete</a>
</li>
</ul>
</div>
</div>
</div>
</li>
</ul>
</div>
</div> --}}
</div>
<div class="col-lg-8 col-xxl-9">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-3">
<div class="card">
<div class="card-body d-flex gap-3 align-items-center">
<div class="avatar-sm">
<div
class="avatar-title border bg-success-subtle border-success border-opacity-25 rounded-2 fs-17">
<i class="ri-product-hunt-line text-success fs-24"></i>
</div>
</div>
<div class="flex-grow-1">
<h5 class="fs-15">{{ $client->products->count() }}</h5>
<p class="mb-0 text-muted">Products</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<div class="text-muted">
<h6 class="mb-3 fw-semibold text-uppercase">Activity Log</h6>
<div class="tab-content text-muted">
<div class="tab-pane active" id="today" role="tabpanel">
<div data-simplebar style="max-height: 400px;">
<div class="profile-timeline">
<div class="accordion accordion-flush" id="todayExample">
@forelse ($client->activityLogs as $index => $log)
<div class="accordion-item border-0">
<div class="accordion-header" id="heading{{ $index }}">
<a class="accordion-button p-2 shadow-none"
data-bs-toggle="collapse"
href="#collapse{{ $index }}"
aria-expanded="false">
<div class="d-flex">
<div class="avatar-xs flex-shrink-0">
<div
class="avatar-title bg-light text-primary rounded-circle">
<i class="ri-history-line"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="fs-14 mb-1">
{{ $log->title }}
</h6>
<small class="text-muted">{!! $log->data !!} -
<span
style="color: #C71585;">{{ \Carbon\Carbon::parse($log->created_at)->format('d M, Y H:i A') }}</span></small>
</div>
</div>
</a>
</div>
<div id="collapse{{ $index }}"
class="accordion-collapse show collapse"
aria-labelledby="heading{{ $index }}"
data-bs-parent="#accordionExample">
<div class="accordion-body ms-2 ps-5">
<div class="row g-2">
<div class="col-auto"></div>
</div>
</div>
</div>
</div>
@empty
@endforelse
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--end card-->
<div class="card">
<div class="card-header">
<div>
<ul class="nav nav-tabs-custom rounded card-header-tabs border-bottom-0" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#products" role="tab">
Products ({{ $client?->products?->count() }})
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#documents" role="tab">
Documents (3)
</a>
</li>
</ul>
<!--end nav-->
</div>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane active" id="products" role="tabpanel">
<div class="table-responsive table-card">
<table class="table align-middle mb-0">
<thead class="table-light text-muted">
<tr>
<th scope="col">Title</th>
<th scope="col">Created At</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
@foreach ($client->products as $product)
<tr>
<th scope="row">
<div class="flex-grow-1 ms-2">
<a href="#" class="fw-medium">{{ $product->name }}</a>
</div>
</th>
<td>{{ getFormatted(date: $product->created_at) }}</td>
@php
$status = $client->status ? 'Published' : 'Draft';
$color = $client->status ? 'success' : 'danger';
@endphp
<td><span
class="badge bg-{{ $color }}-subtle text-{{ $color }}">{{ $status }}</span>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@php
$documents = array_filter([
'logo' => $client->log,
'promised-document' => $client->promised_document,
'poc-document' => $client->poc_document,
]);
@endphp
<div class="tab-pane" id="documents" role="tabpanel">
<div class="table-responsive table-card">
<table class="table table-borderless align-middle mb-0">
<thead class="table-light text-muted">
<tr>
<th scope="col">File Name</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@foreach ($documents as $key => $url)
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm">
<div
class="avatar-title bg-primary-subtle text-primary rounded fs-20">
<i class="ri-file-zip-fill"></i>
</div>
</div>
<div class="ms-3 flex-grow-1">
<h6 class="fs-15 mb-0"><a href="{{ asset($url) }}">{{ $key }}</a></h6>
</div>
</div>
</td>
<td>
<div class="dropdown">
<a href="javascript:void(0);" class="btn btn-light btn-icon"
id="dropdownMenuLink1" data-bs-toggle="dropdown"
aria-expanded="true">
<i class="ri-equalizer-fill"></i>
</a>
<ul class="dropdown-menu dropdown-menu-end"
aria-labelledby="dropdownMenuLink1"
data-popper-placement="bottom-end"
style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 23px);">
<li><a class="dropdown-item" href="{{ asset($url) }}"><i
class="ri-eye-fill me-2 align-middle text-muted"></i>View</a>
</li>
<li><a class="dropdown-item" href="{{ asset($url) }}" download><i
class="ri-download-2-fill me-2 align-middle text-muted"></i>Download</a>
</li>
</ul>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -0,0 +1,7 @@
@extends('client::layouts.master')
@section('content')
<h1>Hello World</h1>
<p>Module: {!! config('client.name') !!}</p>
@endsection

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Client Module - {{ config('app.name', 'Laravel') }}</title>
<meta name="description" content="{{ $description ?? '' }}">
<meta name="keywords" content="{{ $keywords ?? '' }}">
<meta name="author" content="{{ $author ?? '' }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
{{-- Vite CSS --}}
{{-- {{ module_vite('build-client', 'resources/assets/sass/app.scss', storage_path('vite.hot')) }} --}}
</head>
<body>
@yield('content')
{{-- Vite JS --}}
{{-- {{ module_vite('build-client', 'resources/assets/js/app.js', storage_path('vite.hot')) }} --}}
</body>

View File

View File

@@ -0,0 +1,19 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Client\Http\Controllers\ClientController;
/*
*--------------------------------------------------------------------------
* API Routes
*--------------------------------------------------------------------------
*
* Here is where you can register API routes for your application. These
* routes are loaded by the RouteServiceProvider within a group which
* is assigned the "api" middleware group. Enjoy building your API!
*
*/
Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
Route::apiResource('client', ClientController::class)->names('client');
});

View File

@@ -0,0 +1,21 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Client\Http\Controllers\ClientController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::group(['middleware' => ['web', 'auth', 'permission'],'prefix' => 'admin/'], function () {
Route::post('client/reorder', [ClientController::class, 'reorder'])->name('client.reorder');
Route::get('client/toggle/{id}', [ClientController::class, 'toggle'])->name('client.toggle');
Route::resource('client', ClientController::class)->names('client');
});

View File

@@ -0,0 +1,57 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { readdirSync, statSync } from 'fs';
import { join,relative,dirname } from 'path';
import { fileURLToPath } from 'url';
export default defineConfig({
build: {
outDir: '../../public/build-client',
emptyOutDir: true,
manifest: true,
},
plugins: [
laravel({
publicDirectory: '../../public',
buildDirectory: 'build-client',
input: [
__dirname + '/resources/assets/sass/app.scss',
__dirname + '/resources/assets/js/app.js'
],
refresh: true,
}),
],
});
// Scen all resources for assets file. Return array
//function getFilePaths(dir) {
// const filePaths = [];
//
// function walkDirectory(currentPath) {
// const files = readdirSync(currentPath);
// for (const file of files) {
// const filePath = join(currentPath, file);
// const stats = statSync(filePath);
// if (stats.isFile() && !file.startsWith('.')) {
// const relativePath = 'Modules/Client/'+relative(__dirname, filePath);
// filePaths.push(relativePath);
// } else if (stats.isDirectory()) {
// walkDirectory(filePath);
// }
// }
// }
//
// walkDirectory(dir);
// return filePaths;
//}
//const __filename = fileURLToPath(import.meta.url);
//const __dirname = dirname(__filename);
//const assetsDir = join(__dirname, 'resources/assets');
//export const paths = getFilePaths(assetsDir);
//export const paths = [
// 'Modules/Client/resources/assets/sass/app.scss',
// 'Modules/Client/resources/assets/js/app.js',
//];