This commit is contained in:
Simon Pocrnjič 2025-01-02 18:38:47 +01:00
parent 0c3bbfe18f
commit 0f8cfd3f16
41 changed files with 2013 additions and 591 deletions

View File

@ -0,0 +1,42 @@
<?php
namespace App\Events;
use App\Models\ClientCase;
use App\Models\Segment;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ClientCaseToTerrain implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public ClientCase $clientCase;
/**
* Create a new event instance.
*/
public function __construct(ClientCase $clientCase)
{
$this->clientCase = $clientCase;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): PrivateChannel
{
return new PrivateChannel('segments'.$this->clientCase->id);
}
public function broadcastAs(){
return 'client_case.terrain.add';
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Events;
use App\Models\Contract;
use App\Models\Segment;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ContractToTerrain implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public Contract $contract;
public Segment $segment;
/**
* Create a new event instance.
*/
public function __construct(Contract $contract, Segment $segment)
{
//
$this->contract = $contract;
$this->segment = $segment;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): PrivateChannel
{
return new PrivateChannel('contracts.'.$this->segment->id);
}
public function broadcastAs(){
return 'contract.terrain.add';
}
}

View File

@ -131,7 +131,14 @@ public function storeActivity(ClientCase $clientCase, Request $request) {
]);
//Create activity
$clientCase->activities()->create($attributes);
$activity = $clientCase->activities()->create($attributes);
foreach ($activity->decision->events as $e) {
$class = '\\App\\Events\\' . $e->name;
event(new $class($clientCase));
}
return to_route('clientCase.show', $clientCase);
@ -156,6 +163,11 @@ public function show(ClientCase $clientCase)
'person' => fn($que) => $que->with(['addresses', 'phones'])
])->where('active', 1)->findOrFail($clientCase->id);
$types = [
'address_types' => \App\Models\Person\AddressType::all(),
'phone_types' => \App\Models\Person\PhoneType::all()
];
return Inertia::render('Cases/Show', [
'client' => $case->client()->with('person', fn($q) => $q->with(['addresses', 'phones']))->firstOrFail(),
'client_case' => $case,
@ -166,7 +178,8 @@ public function show(ClientCase $clientCase)
->orderByDesc('created_at')
->paginate(20, ['*'], 'activities'),
'contract_types' => \App\Models\ContractType::whereNull('deleted_at')->get(),
'actions' => \App\Models\Action::with('decisions')->get()
'actions' => \App\Models\Action::with('decisions')->get(),
'types' => $types
]);
}

View File

@ -33,6 +33,11 @@ public function show(Client $client, Request $request) {
->with(['person' => fn($que) => $que->with(['addresses','phones'])])
->findOrFail($client->id);
$types = [
'address_types' => \App\Models\Person\AddressType::all(),
'phone_types' => \App\Models\Person\PhoneType::all()
];
return Inertia::render('Client/Show', [
'client' => $data,
'client_cases' => $data->clientCases()
@ -47,6 +52,7 @@ public function show(Client $client, Request $request) {
->orderByDesc('created_at')
->paginate(15)
->withQueryString(),
'types' => $types,
'filters' => $request->only(['search'])
]);
}
@ -91,4 +97,9 @@ public function store(Request $request)
return to_route('client');
}
public function update(Client $client, Request $request) {
return to_route('client.show', $client);
}
}

View File

@ -4,6 +4,7 @@
use App\Models\Person\Person;
use Illuminate\Http\Request;
use Inertia\Inertia;
class PersonController extends Controller
{
@ -19,4 +20,91 @@ public function create(Request $request){
public function store(Request $request){
}
public function update(Person $person, Request $request){
$attributes = $request->validate([
'full_name' => 'string|max:255',
'tax_number' => 'nullable|integer',
'social_security_number' => 'nullable|integer',
'description' => 'nullable|string|max:500'
]);
$person->update($attributes);
return response()->json([
'person' => [
'full_name' => $person->full_name,
'tax_number' => $person->tax_number,
'social_security_number' => $person->social_security_number,
'description' => $person->description
]
]);
}
public function createAddress(Person $person, Request $request){
$attributes = $request->validate([
'address' => 'required|string|max:150',
'country' => 'nullable|string',
'type_id' => 'required|integer|exists:address_types,id',
'description' => 'nullable|string|max:125'
]);
$address_id = $person->addresses()->create($attributes)->id;
return response()->json([
'address' => \App\Models\Person\PersonAddress::with(['type'])->findOrFail($address_id)
]);
}
public function updateAddress(Person $person, int $address_id, Request $request)
{
$attributes = $request->validate([
'address' => 'required|string|max:150',
'country' => 'nullable|string',
'type_id' => 'required|integer|exists:address_types,id',
'description' => 'nullable|string|max:125'
]);
$address = $person->addresses()->with(['type'])->findOrFail($address_id);
$address->update($attributes);
return response()->json([
'address' => $address
]);
}
public function createPhone(Person $person, Request $request)
{
$attributes = $request->validate([
'nu' => 'required|string|max:50',
'country_code' => 'nullable|integer',
'type_id' => 'required|integer|exists:phone_types,id',
'description' => 'nullable|string|max:125'
]);
$phone_id = $person->phones()->create($attributes)->id;
return response()->json([
'phone' => \App\Models\Person\PersonPhone::with(['type'])->findOrFail($phone_id)
]);
}
public function updatePhone(Person $person, int $phone_id, Request $request)
{
$attributes = $request->validate([
'nu' => 'required|string|max:50',
'country_code' => 'nullable|integer',
'type_id' => 'required|integer|exists:phone_types,id',
'description' => 'nullable|string|max:125'
]);
$phone = $person->phones()->with(['type'])->findOrFail($phone_id);
$phone->update($attributes);
return response()->json([
'phone' => $phone
]);
}
}

View File

@ -13,10 +13,10 @@ public function index(Request $request){
return Inertia::render('Settings/Index', [
'actions' => \App\Models\Action::query()
->with('decisions', fn($q) => $q->get(['decisions.id']))
->with('decisions')
->get(),
'decisions' => \App\Models\Decision::query()
->with('actions', fn($q) => $q->get(['actions.id']))
->with('actions')
->get()
]
);

View File

@ -0,0 +1,41 @@
<?php
namespace App\Listeners;
use App\Events\ClientCaseToTerrain;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class AddClientCaseToTerrain
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(ClientCaseToTerrain $event): void
{
$clientCase = $event->clientCase;
$segment = \App\Models\Segment::where('name','terrain')->firstOrFail();
if( $segment ) {
$clientCase->segments()->detach($segment->id);
$clientCase->segments()->attach(
$segment->id,
);
\Log::info("Added contract to terrain", ['contract_id' => $clientCase->id, 'segment' => $segment->name ]);
}
}
public function failed(ClientCaseToTerrain $event, $exception)
{
\Log::error('Failed to update inventory', ['contract_id' => $event->clientCase->id, 'error' => $exception->getMessage()]);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Listeners;
use App\Events\ContractToTerrain;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class AddContractToTerrain implements ShouldQueue
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(ContractToTerrain $event): void
{
$contract = $event->contract;
$segment = $event->segment->where('name', 'terrain')->firstOrFail();
if($segment) {
$contract->segments()->attach($segment->id);
//\Log::info("Added contract to terrain", ['contract_id' => $contract->id, 'segment' => $segment->name ]);
}
}
public function failed(ContractToTerrain $event, $exception)
{
//\Log::error('Failed to update inventory', ['contract_id' => $event->contract->id, 'error' => $exception->getMessage()]);
}
}

View File

@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Scout\Searchable;
@ -47,7 +48,8 @@ public function client(): BelongsTo
public function person(): BelongsTo
{
return $this->belongsTo(\App\Models\Person\Person::class);
return $this->belongsTo(\App\Models\Person\Person::class)
->with(['phones', 'addresses']);
}
public function contracts(): HasMany
@ -59,4 +61,8 @@ public function activities(): HasMany
{
return $this->hasMany(\App\Models\Activity::class);
}
public function segments(): BelongsToMany {
return $this->belongsToMany(\App\Models\Segment::class)->withTimestamps();
}
}

View File

@ -3,10 +3,15 @@
namespace App\Models;
use App\Traits\Uuid;
use Illuminate\Database\Eloquent\Factories\BelongsToManyRelationship;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasOneOrManyThrough;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\SoftDeletes;
class Contract extends Model
@ -36,17 +41,16 @@ public function type(): BelongsTo
return $this->belongsTo(\App\Models\ContractType::class, 'type_id');
}
public function client(): BelongsTo
public function clientCase(): BelongsTo
{
return $this->belongsTo(\App\Models\Person\Person::class, 'client_id');
return $this->belongsTo(\App\Models\ClientCase::class)
->with(['person']);
}
public function debtor(): BelongsTo
{
return $this->belongsTo(\App\Models\Person\Person::class, 'debtor_id');
}
public function segments(): BelongsToMany {
return $this->belongsToMany(\App\Models\Segment::class);
return $this->belongsToMany(\App\Models\Segment::class)
->withPivot('active', 'created_at')
->wherePivot('active', true);
}
}

View File

@ -4,9 +4,15 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Event extends Model
{
/** @use HasFactory<\Database\Factories\EventFactory> */
use HasFactory;
public function decisions(): BelongsToMany
{
return $this->belongsToMany(\App\Models\Decision::class);
}
}

View File

@ -76,14 +76,16 @@ public function phones(): HasMany
{
return $this->hasMany(\App\Models\Person\PersonPhone::class)
->with(['type'])
->where('active','=',1);
->where('active','=',1)
->orderBy('id');
}
public function addresses(): HasMany
{
return $this->hasMany(\App\Models\Person\PersonAddress::class)
->with(['type'])
->where('active','=',1);
->where('active','=',1)
->orderBy('id');
}
public function group(): BelongsTo

View File

@ -14,4 +14,8 @@ class Segment extends Model
public function contracts(): BelongsToMany {
return $this->belongsToMany(\App\Models\Contract::class);
}
public function clientCase(): BelongsToMany {
return $this->belongsToMany(\App\Models\ClientCase::class)->withTimestamps();
}
}

View File

@ -0,0 +1,31 @@
<?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('client_case_segment', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(\App\Models\ClientCase::class)->constrained()->onDelete('cascade');
$table->foreignIdFor(\App\Models\Segment::class)->constrained()->onDelete('cascade');
$table->boolean('active')->default(true);
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('client_case_segment');
}
};

View File

@ -0,0 +1,29 @@
<?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('decision_event', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(\App\Models\Decision::class);
$table->foreignIdFor(\App\Models\Event::class);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('decision_event');
}
};

View File

@ -26,7 +26,8 @@ public function run(): void
$this->call([
PersonSeeder::class,
SegmentSeeder::class,
ActionSeeder::class
ActionSeeder::class,
EventSeeder::class
]);
}
}

View File

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\Event;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
@ -12,6 +13,12 @@ class EventSeeder extends Seeder
*/
public function run(): void
{
//
$events = [
[ 'name' => 'client_case.terrain.add', 'options' => json_encode([]) ]
];
foreach($events as $e) {
Event::create($e);
}
}
}

View File

@ -24,7 +24,7 @@ public function run(): void
];
$personGroups = [
[ 'name' => 'naročnik', 'description' => '', 'color_tag' => 'blue-400'],
[ 'name' => 'naročnik', 'description' => '', 'color_tag' => 'blue-500'],
[ 'name' => 'primer naročnika', 'description' => '', 'color_tag' => 'red-400']
];

21
package-lock.json generated
View File

@ -18,7 +18,9 @@
"flowbite-vue": "^0.1.6",
"lodash": "^4.17.21",
"material-design-icons-iconfont": "^6.7.0",
"preline": "^2.7.0",
"tailwindcss-inner-border": "^0.2.0",
"vue-multiselect": "^3.1.0",
"vue-search-input": "^1.1.16",
"vue3-apexcharts": "^1.7.0",
"vuedraggable": "^4.1.0"
@ -2837,6 +2839,15 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/preline": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/preline/-/preline-2.7.0.tgz",
"integrity": "sha512-xMuMVZ7aftBT1/5/3Rb48i/t+LWQfsUjBX7bls4peqS6h5OZTbIgz0N6eMDzuDlOZF3DiSWLehl00oGlAkvovw==",
"license": "Licensed under MIT and Preline UI Fair Use License",
"dependencies": {
"@popperjs/core": "^2.11.2"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -3552,6 +3563,16 @@
}
}
},
"node_modules/vue-multiselect": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vue-multiselect/-/vue-multiselect-3.1.0.tgz",
"integrity": "sha512-+i/fjTqFBpaay9NP+lU7obBeNaw2DdFDFs4mqhsM0aEtKRdvIf7CfREAx2o2B4XDmPrBt1r7x1YCM3BOMLaUgQ==",
"license": "MIT",
"engines": {
"node": ">= 14.18.1",
"npm": ">= 6.14.15"
}
},
"node_modules/vue-resize": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz",

View File

@ -33,7 +33,9 @@
"flowbite-vue": "^0.1.6",
"lodash": "^4.17.21",
"material-design-icons-iconfont": "^6.7.0",
"preline": "^2.7.0",
"tailwindcss-inner-border": "^0.2.0",
"vue-multiselect": "^3.1.0",
"vue-search-input": "^1.1.16",
"vue3-apexcharts": "^1.7.0",
"vuedraggable": "^4.1.0"

View File

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

View File

@ -0,0 +1,197 @@
<script setup>
import { ref, watch } from 'vue';
import Drawer from './Drawer.vue';
import InputLabel from './InputLabel.vue';
import SectionTitle from './SectionTitle.vue';
import TextInput from './TextInput.vue';
import InputError from './InputError.vue';
import PrimaryButton from './PrimaryButton.vue';
const props = defineProps({
show: {
type: Boolean,
default: false
},
person: Object,
types: Array,
edit: {
type: Boolean,
default: false
},
id: {
type: Number,
default: 0
}
});
const processing = ref(false);
const errors = ref({});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
setTimeout(() => {
errors.value = {};
}, 500);
}
const form = ref({
address: '',
country: '',
type_id: props.types[0].id,
description: ''
});
const resetForm = () => {
form.value = {
address: '',
country: '',
type_id: props.types[0].id,
description: ''
};
}
const create = async () => {
processing.value = true;
errors.value = {};
const data = await axios({
method: 'post',
url: route('person.address.create', props.person),
data: form.value
}).then((response) => {
props.person.addresses.push(response.data.address);
processing.value = false;
close();
resetForm();
}).catch((reason) => {
errors.value = reason.response.data.errors;
processing.value = false;
});
}
const update = async () => {
processing.value = true;
errors.value = {};
const data = await axios({
method: 'put',
url: route('person.address.update', {person: props.person, address_id: props.id}),
data: form.value
}).then((response) => {
console.log(response.data.address)
const index = props.person.addresses.findIndex( a => a.id === response.data.address.id );
props.person.addresses[index] = response.data.address;
processing.value = false;
close();
resetForm();
}).catch((reason) => {
errors.value = reason.response.data.errors;
processing.value = false;
});
}
watch(
() => props.id,
((id) => {
if (props.edit && id !== 0){
console.log(props.edit)
props.person.addresses.filter((a) => {
if(a.id === props.id){
form.value = {
address: a.address,
country: a.country,
type_id: a.type_id,
description: a.description
};
}
});
return;
}
resetForm();
})
);
const callSubmit = () => {
if( props.edit ) {
update();
} else {
create();
}
}
</script>
<template>
<Drawer
:show="show"
@close="close"
>
<template #title>
<span v-if="edit">Spremeni naslov</span>
<span v-else>Dodaj novi naslov</span>
</template>
<template #content>
<form @submit.prevent="callSubmit">
<SectionTitle class="border-b mb-4">
<template #title>
Naslov
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cr_address" value="Naslov" />
<TextInput
id="cr_address"
ref="cr_addressInput"
v-model="form.address"
type="text"
class="mt-1 block w-full"
autocomplete="address"
/>
<InputError v-if="errors.address !== undefined" v-for="err in errors.address" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cr_country" value="Država" />
<TextInput
id="cr_country"
ref="cr_countryInput"
v-model="form.country"
type="text"
class="mt-1 block w-full"
autocomplete="country"
/>
<InputError v-if="errors.address !== undefined" v-for="err in errors.address" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cr_type" value="Tip"/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="cr_type"
v-model="form.type_id"
>
<option v-for="type in types" :key="type.id" :value="type.id">{{ type.name }}</option>
</select>
</div>
<div class="flex justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': processing }" :disabled="processing">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -0,0 +1,17 @@
<template>
<div></div>
</template>
<script>
export default {
name: "Test",
created() {},
data() {
return {};
},
props: {},
methods: {},
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,25 @@
<script setup>
import { inject } from 'vue';
defineProps({
name: {
type: String,
default: ''
},
title: {
stype: String,
default: ''
}
});
const selected = inject('selected');
</script>
<template>
<div class="py-4 px-2" role="tabpanel" v-show="selected === name">
<slot />
</div>
</template>

View File

@ -0,0 +1,38 @@
<script setup>
import { provide, ref, useSlots } from 'vue';
defineProps({
selectedColor: {
type: String,
default: 'blue-600'
}
});
const slots = useSlots();
const tabs = ref(slots.default().map((tab) => tab.props ));
const selected = ref(tabs.value[0].name);
provide('selected', selected);
</script>
<template>
<div class="text-sm font-medium text-center text-gray-500 border-b border-gray-200 dark:text-gray-400 dark:border-gray-700">
<ul class="flex flex-wrap -mb-px">
<li class="me-2" v-for="tab in tabs" :key="tab.name">
<button
@click="selected = tab.name"
class="inline-block p-4"
:class="{
['border-b-2 border-transparent rounded-t-lg hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300']: tab.name !== selected,
[`text-${ selectedColor } border-b-2 border-${ selectedColor } rounded-t-lg active dark:text-blue-500 dark:border-${ selectedColor }`]: tab.name === selected
}"
>
{{ tab.title }}
</button>
</li>
</ul>
</div>
<div id="default-tab-content">
<slot />
</div>
</template>

View File

@ -1,43 +1,189 @@
<script setup>
import { FwbBadge } from 'flowbite-vue';
import { EditIcon, PlusIcon, UserEditIcon } from '@/Utilities/Icons';
import CusTab from './CusTab.vue';
import CusTabs from './CusTabs.vue';
import { provide, ref, watch } from 'vue';
import PersonUpdateForm from './PersonUpdateForm.vue';
import AddressCreateForm from './AddressCreateForm.vue';
import PhoneCreateForm from './PhoneCreateForm.vue';
const props = defineProps({
person: Object
})
person: Object,
edit: {
type: Boolean,
default: false
},
tabColor: {
type: String,
default: 'blue-600'
},
types: {
type: Object,
default: {
address_types: [],
phone_types: []
}
}
});
const drawerUpdatePerson = ref(false);
const drawerAddAddress = ref(false);
const drawerAddPhone = ref(false);
const editAddress = ref(false);
const editAddressId = ref(0);
const editPhone = ref(false);
const editPhoneId = ref(0);
const getMainAddress = (adresses) => {
const addr = adresses.filter( a => a.type.id === 1 )[0] ?? '';
if( addr !== '' ){
const country = addr.country !== '' ? ` - ${addr.country}` : '';
return addr.address !== '' ? addr.address + country : '';
}
return '';
}
const getMainPhone = (phones) => {
const pho = phones.filter( a => a.type.id === 1 )[0] ?? '';
if( pho !== '' ){
const countryCode = pho.country_code !== null ? `+${pho.country_code} ` : '';
return pho.nu !== '' ? countryCode + pho.nu: '';
}
return '';
}
const openDrawerUpdateClient = () => {
drawerUpdatePerson.value = true;
}
const openDrawerAddAddress = (edit = false, id = 0) => {
drawerAddAddress.value = true;
editAddress.value = edit;
editAddressId.value = id;
}
const operDrawerAddPhone = (edit = false, id = 0) => {
drawerAddPhone.value = true;
editPhone.value = edit;
editPhoneId.value = id;
}
</script>
<template>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
<div class="rounded p-2 shadow">
<p class="text-xs leading-5 md:text-sm text-gray-500">Nu.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.nu }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Name.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.full_name }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Tax NU.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.tax_number }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Social security NU.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.social_security_number }}</p>
</div>
</div>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mt-1">
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Address</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.main_address }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Phone</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.main_phone }}</p>
</div>
<div class="md:col-span-full lg:col-span-1 rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Description</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.description }}</p>
</div>
</div>
<CusTabs :selected-color="tabColor">
<CusTab name="person" title="Oseba">
<div class="flex justify-end mb-2">
<span class=" border-b-2 border-gray-500 hover:border-gray-800">
<button @click="openDrawerUpdateClient"><UserEditIcon size="lg" css="text-gray-500 hover:text-gray-800" /></button>
</span>
</div>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
<div class="rounded p-2 shadow">
<p class="text-xs leading-5 md:text-sm text-gray-500">Nu.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.nu }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Name.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.full_name }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Tax NU.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.tax_number }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Social security NU.</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.social_security_number }}</p>
</div>
</div>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mt-1">
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Address</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ getMainAddress(person.addresses) }}</p>
</div>
<div class="rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Phone</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ getMainPhone(person.phones) }}</p>
</div>
<div class="md:col-span-full lg:col-span-1 rounded p-2 shadow">
<p class="text-sm leading-5 md:text-sm text-gray-500">Description</p>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ person.description }}</p>
</div>
</div>
</CusTab >
<CusTab name="addresses" title="Naslovi">
<div class="flex justify-end mb-2">
<span class="border-b-2 border-gray-500 hover:border-gray-800">
<button><PlusIcon @click="openDrawerAddAddress(false, 0)" size="lg" css="text-gray-500 hover:text-gray-800" /></button>
</span>
</div>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mt-1">
<div class="rounded p-2 shadow" v-for="address in person.addresses">
<div class="text-sm leading-5 md:text-sm text-gray-500 flex justify-between">
<div class="flex ">
<FwbBadge type="yellow">{{ address.country }}</FwbBadge>
<FwbBadge>{{ address.type.name }}</FwbBadge>
</div>
<button><EditIcon @click="openDrawerAddAddress(true, address.id)" size="md" css="text-gray-500 hover:text-gray-800" /></button>
</div>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ address.address }}</p>
</div>
</div>
</CusTab>
<CusTab name="phones" title="Telefonske">
<div class="flex justify-end mb-2">
<span class="border-b-2 border-gray-500 hover:border-gray-800">
<button><PlusIcon @click="operDrawerAddPhone(false, 0)" size="lg" css="text-gray-500 hover:text-gray-800" /></button>
</span>
</div>
<div class="grid grid-rows-* grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mt-1">
<div class="rounded p-2 shadow" v-for="phone in person.phones">
<div class="text-sm leading-5 md:text-sm text-gray-500 flex justify-between">
<div class="flex ">
<FwbBadge title type="yellow">+{{ phone.country_code }}</FwbBadge>
<FwbBadge>{{ phone.type.name }}</FwbBadge>
</div>
<button><EditIcon @click="operDrawerAddPhone(true, phone.id)" size="md" css="text-gray-500 hover:text-gray-800" /></button>
</div>
<p class="text-sm md:text-base leading-7 text-gray-900">{{ phone.nu }}</p>
</div>
</div>
</CusTab>
<CusTab name="other" title="Drugo">
ssss4
</CusTab>
</CusTabs>
<PersonUpdateForm
:show="drawerUpdatePerson"
@close="drawerUpdatePerson = false"
:person="person"
/>
<AddressCreateForm
:show="drawerAddAddress"
@close="drawerAddAddress = false"
:person="person"
:types="types.address_types"
:id="editAddressId"
:edit="editAddress"
/>
<PhoneCreateForm
:show="drawerAddPhone"
@close="drawerAddPhone = false"
:person="person"
:types="types.phone_types"
:id="editPhoneId"
:edit="editPhone"
/>
</template>

View File

@ -0,0 +1,139 @@
<script setup>
import Drawer from '@/Components/Drawer.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SectionTitle from '@/Components/SectionTitle.vue';
import TextInput from '@/Components/TextInput.vue';
import axios from 'axios';
import { inject, onMounted, ref } from 'vue';
import InputError from './InputError.vue';
const props = defineProps({
show: {
type: Boolean,
default: false
},
person: Object
});
const processingUpdate = ref(false);
const errors = ref({});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
setTimeout(() => {
errors.value = {};
}, 500);
}
const form = ref({
full_name: props.person.full_name,
tax_number: props.person.tax_number,
social_security_number: props.person.social_security_number,
description: props.person.description
});
const updatePerson = () => {
processingUpdate.value = true;
errors.value = {};
axios({
method: 'put',
url: route('person.update', props.person ),
data: form.value
}).then((response) => {
props.person.full_name = response.data.person.full_name;
props.person.tax_number = response.data.person.tax_number;
props.person.social_security_number = response.data.person.social_security_number;
props.person.description = response.data.person.description;
processingUpdate.value = false;
close();
}).catch((reason) => {
console.log(reason.response.data);
errors.value = reason.response.data.errors;
processingUpdate.value = false;
});
}
</script>
<template>
<Drawer
:show="show"
@close="close"
>
<template #title>Posodobi {{ person.full_name }}</template>
<template #content>
<form @submit.prevent="updatePerson">
<SectionTitle class="border-b mb-4">
<template #title>
Oseba
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cfulname" value="Naziv" />
<TextInput
id="cfullname"
ref="cfullnameInput"
v-model="form.full_name"
type="text"
class="mt-1 block w-full"
autocomplete="full-name"
/>
<InputError v-if="errors.full_name !== undefined" v-for="err in errors.full_name" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="ctaxnumber" value="Davčna" />
<TextInput
id="ctaxnumber"
ref="ctaxnumberInput"
v-model="form.tax_number"
type="text"
class="mt-1 block w-full"
autocomplete="tax-number"
/>
<InputError v-if="errors.tax_number !== undefined" v-for="err in errors.tax_number" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="csocialSecurityNumber" value="Matična / Emšo" />
<TextInput
id="csocialSecurityNumber"
ref="csocialSecurityNumberInput"
v-model="form.social_security_number"
type="text"
class="mt-1 block w-full"
autocomplete="social-security-number"
/>
<InputError v-if="errors.social_security_number !== undefined" v-for="err in errors.social_security_number" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cdescription" value="Opis"/>
<TextInput
id="cdescription"
ref="cdescriptionInput"
v-model="form.description"
type="text"
class="mt-1 block w-full"
autocomplete="description"
/>
<InputError v-if="errors.description !== undefined" v-for="err in errors.description" :message="err" />
</div>
<div class="flex justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': processingUpdate }" :disabled="processingUpdate">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -0,0 +1,193 @@
<script setup>
import { ref, watch } from 'vue';
import Drawer from './Drawer.vue';
import InputLabel from './InputLabel.vue';
import SectionTitle from './SectionTitle.vue';
import TextInput from './TextInput.vue';
import InputError from './InputError.vue';
import PrimaryButton from './PrimaryButton.vue';
import axios from 'axios';
const props = defineProps({
show: {
type: Boolean,
default: false
},
person: Object,
types: Array,
edit: {
type: Boolean,
default: false
},
id: {
type: Number,
default: 0
}
});
const processing = ref(false);
const errors = ref({});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
setTimeout(() => {
errors.value = {};
}, 500);
}
const form = ref({
nu: '',
country_code: 386,
type_id: props.types[0].id,
description: ''
});
const resetForm = () => {
form.value = {
nu: '',
country_code: 386,
type_id: props.types[0].id,
description: ''
}
};
const create = async () => {
processing.value = true;
errors.value = {};
const data = await axios({
method: 'post',
url: route('person.phone.create', props.person),
data: form.value
}).then((response) => {
props.person.phones.push(response.data.phone);
processing.value = false;
close();
resetForm();
}).catch((reason) => {
errors.value = reason.response.data.errors;
processing.value = false;
});
};
const update = async () => {
processing.value = true;
errors.value = {};
const data = await axios({
method: 'put',
url: route('person.phone.update', {person: props.person, phone_id: props.id}),
data: form.value
}).then((response) => {
const index = props.person.phones.findIndex( p => p.id === response.data.phone.id );
props.person.phones[index] = response.data.phone;
processing.value = false;
close();
resetForm();
}).catch((reason) => {
errors.value = reason.response.data.errors;
processing.value = false;
});
}
watch(
() => props.id,
((id) => {
if ( id !== 0 && props.edit ){
const index = props.person.phones.findIndex( p => p.id === id );
form.value.nu = props.person.phones[index].nu;
form.value.country_code = props.person.phones[index].country_code;
form.value.type_id = props.person.phones[index].type_id;
form.value.description = props.person.phones[index].description;
return;
}
resetForm();
})
);
const submit = () => {
if( props.edit ) {
update();
} else {
create();
}
}
</script>
<template>
<Drawer
:show="show"
@close="close"
>
<template #title>
<span v-if="edit">Spremeni telefon</span>
<span v-else>Dodaj novi telefon</span>
</template>
<template #content>
<form @submit.prevent="submit">
<SectionTitle class="border-b mb-4">
<template #title>
Telefon
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="pp_nu" value="Številka" />
<TextInput
id="pp_nu"
ref="pp_nuInput"
v-model="form.nu"
type="text"
class="mt-1 block w-full"
autocomplete="nu"
/>
<InputError v-if="errors.nu !== undefined" v-for="err in errors.nu" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="pp_countrycode" value="Koda države tel." />
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="pp_countrycode"
v-model="form.country_code"
>
<option value="386">+386 (Slovenija)</option>
<option value="385">+385 (Hrvaška)</option>
<option value="39">+39 (Italija)</option>
<option value="36">+39 (Madžarska)</option>
<option value="43">+43 (Avstrija)</option>
<option value="381">+381 (Srbija)</option>
<option value="387">+387 (Bosna in Hercegovina)</option>
<option value="382">+382 (Črna gora)</option>
<!-- ... -->
</select>
<InputError v-if="errors.country_code !== undefined" v-for="err in errors.country_code" :message="err" />
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="pp_type" value="Tip"/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="pp_type"
v-model="form.type_id"
>
<option v-for="type in types" :key="type.id" :value="type.id">{{ type.name }}</option>
</select>
</div>
<div class="flex justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': processing }" :disabled="processing">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -0,0 +1,17 @@
<template>
<div></div>
</template>
<script>
export default {
name: "Test",
created() {},
data() {
return {};
},
props: {},
methods: {},
};
</script>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, ref, watch } from 'vue';
import { ref } from 'vue';
import { Head, Link, router, usePage } from '@inertiajs/vue3';
import ApplicationMark from '@/Components/ApplicationMark.vue';
import Banner from '@/Components/Banner.vue';
@ -8,66 +8,19 @@ import DropdownLink from '@/Components/DropdownLink.vue';
import NavLink from '@/Components/NavLink.vue';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink.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';
import GlobalSearch from './Partials/GlobalSearch.vue';
const props = defineProps({
title: String,
});
const showingNavigationDropdown = ref(false);
const querySearchDiv = ref(null);
const querySearch = ref('');
const querySearchList = ref(true);
const querySearchResult = ref([]);
const logout = () => {
router.post(route('logout'));
};
const onSearchFocus = () => {
//if(querySearch.value.length > 0) {
querySearchList.value = false;
//}
}
const onSearchBlue = () => {
setTimeout(() => querySearchList.value = true, 300)
}
const searchDebounce = 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);
watch(querySearch, (value) => {
searchDebounce(value);
});
</script>
<template>
@ -111,47 +64,14 @@ watch(querySearch, (value) => {
Nastavitve
</NavLink>
</div>
<div class="hidden space-x-8 sm:-my-px sm:items-center sm:ms-10 sm:flex">
<GlobalSearch />
</div>
</div>
<div class="hidden sm:flex sm:items-center sm:ms-6">
<!-- Settings Dropdown -->
<div>
<fwb-input
v-model="querySearch"
placeholder="Iskalnik..."
size="md"
@focus="onSearchFocus"
@blur="onSearchBlue"
@input.stop="true"
>
<template #prefix>
<SearchIcon />
</template>
</fwb-input>
<fwb-list-group ref="querySearchDiv" class="absolute" :class="(querySearchList) ? 'hidden' : ''">
<fwb-list-group-item class="px-0 flex flex-col">
<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.client_uuid})">{{ client.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</fwb-list-group-item>
<fwb-list-group-item class="px-0 flex flex-col">
<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.case_uuid})">{{ clientCase.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</fwb-list-group-item>
</fwb-list-group>
</div>
<div class="ms-3 relative">
<Dropdown align="right" width="48">
<template #trigger>
@ -314,50 +234,13 @@ watch(querySearch, (value) => {
</template>
</template>
</div>
<!--div class="px-2 w-full relative">
<fwb-input
v-model="querySearch"
placeholder="Iskalnik..."
size="md"
@focus="onSearchFocus"
@blur="onSearchBlue"
@input.stop="true"
>
<template #prefix>
<SearchIcon />
</template>
</fwb-input>
<fwb-list-group ref="querySearchDiv" class="absolute px-2 w-full mx-auto" :hidden="querySearchList">
<fwb-list-group-item class="px-0 flex flex-col">
<p>Naročniki:</p>
<fwb-list-group class="w-full">
<fwb-list-group-item hover v-for="client in querySearchResult.clients">
<a :href="route('client.show', {uuid: client.client_uuid})">{{ client.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</fwb-list-group-item>
<fwb-list-group-item class="px-0 flex flex-col">
<p>Primeri:</p>
<fwb-list-group class="w-full">
<fwb-list-group-item hover v-for="clientCase in querySearchResult.client_cases">
<a :href="route('clientCase.show', {uuid: clientCase.case_uuid})">{{ clientCase.full_name }}</a>
</fwb-list-group-item>
</fwb-list-group>
</fwb-list-group-item>
</fwb-list-group>
</div-->
</div>
</div>
</nav>
<!-- Page Heading -->
<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">
<header v-if="$slots.header" class="bg-white shadow">
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<Breadcrumbs :breadcrumbs="$page.props.breadcrumbs"></Breadcrumbs>
</div>

View File

@ -0,0 +1,72 @@
<script setup>
import { FwbInput, FwbListGroup, FwbListGroupItem } from 'flowbite-vue';
import axios from 'axios';
import { debounce } from 'lodash';
import { SearchIcon } from '@/Utilities/Icons';
import { ref, watch } from 'vue';
import Dropdown from '@/Components/Dropdown.vue';
import DropdownLink from '@/Components/DropdownLink.vue';
const props = defineProps({
css: String
});
const query = ref('');
const result = ref([]);
const searching = debounce((value) => {
axios.get(
route('search'),
{
params: {
query: value,
limit: 5,
tag: ''
}
}
)
.then(function(res) {
result.value = res.data
list.value = false;
console.log(res);
})
.catch(function(error){
console.log(error)
})
.finally(function(){
});
}, 300);
watch(
() => query.value,
(val) => searching(val)
);
</script>
<template>
<Dropdown align="left" :contentClasses="['py-1 bg-white lg:w-60']">
<template #trigger>
<fwb-input
v-model="query"
placeholder="Iskalnik..."
size="sm"
class="lg:w-60"
>
<template #prefix>
<SearchIcon />
</template>
</fwb-input>
</template>
<template #content>
<div class="block px-4 py-2 text-xs text-gray-400">Naročnik</div>
<DropdownLink v-for="client in result.clients" :href="route('client.show', {uuid: client.client_uuid})">{{ client.full_name }}</DropdownLink>
<div class="border-t border-gray-200" />
<div class="block px-4 py-2 text-xs text-gray-400">Cases</div>
<DropdownLink v-for="clientcase in result.client_cases" :href="route('clientCase.show', {uuid: clientcase.case_uuid})">{{ clientcase.full_name }}</DropdownLink>
</template>
</Dropdown>
</template>

View File

@ -1,185 +1,161 @@
<script setup>
import PersonInfoGrid from '@/Components/PersonInfoGrid.vue';
import SectionTitle from '@/Components/SectionTitle.vue';
import AppLayout from '@/Layouts/AppLayout.vue';
import { Link, useForm } from '@inertiajs/vue3';
import { FwbA, FwbButton } from 'flowbite-vue';
import { ref } from 'vue';
import ContractDrawer from './Partials/ContractDrawer.vue';
import ContractTable from './Partials/ContractTable.vue';
import ActivityDrawer from './Partials/ActivityDrawer.vue';
import ActivityTable from './Partials/ActivityTable.vue';
import PersonInfoGrid from "@/Components/PersonInfoGrid.vue";
import SectionTitle from "@/Components/SectionTitle.vue";
import AppLayout from "@/Layouts/AppLayout.vue";
import { FwbButton } from "flowbite-vue";
import { onBeforeMount, ref } from "vue";
import ContractDrawer from "./Partials/ContractDrawer.vue";
import ContractTable from "./Partials/ContractTable.vue";
import ActivityDrawer from "./Partials/ActivityDrawer.vue";
import ActivityTable from "./Partials/ActivityTable.vue";
import { AngleDownIcon, AngleUpIcon } from "@/Utilities/Icons";
const props = defineProps({
client: Object,
client_case: Object,
contracts: Array,
activities: Object,
contract_types: Array,
actions: Array
client: Object,
client_case: Object,
contracts: Array,
activities: Object,
contract_types: Array,
actions: Array,
types: Object
});
console.log(props.actions);
const getMainAddress = (adresses) => {
const addr = adresses.filter( a => a.type.id === 1 )[0] ?? '';
const country = addr.country !== '' ? ` - ${addr.country}` : '';
return addr.address !== '' ? addr.address + country : '';
}
const getMainPhone = (phones) => {
const pho = phones.filter( a => a.type.id === 1 )[0] ?? '';
const countryCode = pho.country_code !== null ? `+${pho.country_code} ` : '';
return pho.nu !== '' ? countryCode + pho.nu: '';
}
const clientInfo = new Object({
nu: props.client.person.nu,
full_name: props.client.person.full_name,
main_address: getMainAddress(props.client.person.addresses),
main_phone: getMainPhone(props.client.person.phones),
tax_number: props.client.person.tax_number,
social_security_number: props.client.person.social_security_number,
description: props.client.person.description
});
const casePersonInfo = new Object({
nu: props.client_case.person.nu,
full_name: props.client_case.person.full_name,
main_address: getMainAddress(props.client_case.person.addresses),
main_phone: getMainPhone(props.client_case.person.phones),
tax_number: props.client_case.person.tax_number,
social_security_number: props.client_case.person.social_security_number,
description: props.client_case.person.description
});
const clientDetails = ref(true);
//Drawer add new contract
const drawerCreateContract = ref(false);
const openDrawerCreateContract = () => {
drawerCreateContract.value = true;
}
drawerCreateContract.value = true;
};
//Drawer add new activity
const drawerAddActivity = ref(false);
const openDrawerAddActivity = () => {
drawerAddActivity.value = true;
}
drawerAddActivity.value = true;
};
//Close drawer (all)
const closeDrawer = () => {
drawerCreateContract.value = false;
drawerAddActivity.value = false;
}
drawerCreateContract.value = false;
drawerAddActivity.value = false;
};
const showClientDetails = () => {
clientDetails.value = false;
};
const hideClietnDetails = () => {
clientDetails.value = true;
};
</script>
<template>
<AppLayout title="Client case">
<template #header></template>
<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 border-l-4 border-blue-400">
<div class="mx-auto max-w-4x1 p-3">
<SectionTitle>
<template #title>
<FwbA class= "hover:text-blue-500" :href="route('client.show', client)">
{{ clientInfo.full_name }}
</FwbA>
</template>
</SectionTitle>
</div>
</div>
</div>
<AppLayout title="Client case">
<template #header></template>
<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 border-l-4 border-blue-500"
>
<div class="mx-auto max-w-4x1 p-3 flex justify-between">
<SectionTitle>
<template #title>
<a class="hover:text-blue-500" :href="route('client.show', client)">
{{ client.person.full_name }}
</a>
</template>
</SectionTitle>
<button @click="showClientDetails" :hidden="clientDetails ? false : true">
<AngleUpIcon />
</button>
<button :hidden="clientDetails" @click="hideClietnDetails">
<AngleDownIcon />
</button>
</div>
</div>
<!--div class="pt-1">
<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="mx-auto max-w-4x1 p-3">
<PersonInfoGrid :person="clientInfo" />
</div>
</div>
</div>
</div-->
<div class="pt-6">
<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="mx-auto max-w-4x1 p-3">
<SectionTitle>
<template #title>
Primer - oseba
</template>
</SectionTitle>
</div>
</div>
</div>
</div>
</div>
<div class="pt-1" :hidden="clientDetails">
<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="mx-auto max-w-4x1 p-3">
<PersonInfoGrid :types="types" :person="client.person" />
</div>
</div>
<div class="pt-1">
<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="mx-auto max-w-4x1 p-3">
<PersonInfoGrid :person="casePersonInfo" />
</div>
</div>
</div>
</div>
</div>
<div class="pt-6">
<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="mx-auto max-w-4x1 p-3">
<SectionTitle>
<template #title> Primer - oseba </template>
</SectionTitle>
</div>
</div>
<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 border-l-4">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-3">
<SectionTitle>
<template #title>
Pogodbe
</template>
</SectionTitle>
<FwbButton @click="openDrawerCreateContract">Nova</FwbButton>
</div>
<ContractTable
:client_case="client_case"
:contracts="contracts"
:contract_types="contract_types"
/>
</div>
</div>
</div>
</div>
</div>
<div class="pt-1">
<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="mx-auto max-w-4x1 p-3">
<PersonInfoGrid :types="types" tab-color="red-600" :person="client_case.person" />
</div>
</div>
<div class="pt-12 pb-6">
<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">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-3">
<SectionTitle>
<template #title>
Aktivnosti
</template>
</SectionTitle>
<FwbButton @click="openDrawerAddActivity">Nova</FwbButton>
</div>
<ActivityTable
:client_case="client_case"
:activities="activities"
/>
</div>
</div>
</div>
</div>
<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 border-l-4">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-3">
<SectionTitle>
<template #title> Pogodbe </template>
</SectionTitle>
<FwbButton @click="openDrawerCreateContract">Nova</FwbButton>
</div>
<ContractTable
:client_case="client_case"
:contracts="contracts"
:contract_types="contract_types"
/>
</div>
</div>
</AppLayout>
<ContractDrawer
:show="drawerCreateContract"
@close="closeDrawer"
:types="contract_types"
:client_case="client_case"
/>
<ActivityDrawer
:show="drawerAddActivity"
@close="closeDrawer"
:client_case="client_case"
:actions="actions"
/>
</template>
</div>
</div>
<div class="pt-12 pb-6">
<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">
<div class="mx-auto max-w-4x1">
<div class="flex justify-between p-3">
<SectionTitle>
<template #title> Aktivnosti </template>
</SectionTitle>
<FwbButton @click="openDrawerAddActivity">Nova</FwbButton>
</div>
<ActivityTable :client_case="client_case" :activities="activities" />
</div>
</div>
</div>
</div>
</AppLayout>
<ContractDrawer
:show="drawerCreateContract"
@close="closeDrawer"
:types="contract_types"
:client_case="client_case"
/>
<ActivityDrawer
:show="drawerAddActivity"
@close="closeDrawer"
:client_case="client_case"
:actions="actions"
/>
</template>

View File

@ -0,0 +1,201 @@
<script setup>
import Drawer from '@/Components/Drawer.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import InputLabel from '@/Components/InputLabel.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import SectionTitle from '@/Components/SectionTitle.vue';
import ActionMessage from '@/Components/ActionMessage.vue';
const props = defineProps({
show: {
type: Boolean,
default: false
},
clientUuid: String
});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
}
const Address = {
address: '',
country: '',
type_id: 1
}
const Phone = {
nu: '',
country_code: '00386',
type_id: 1
}
const Person = {
first_name: '',
last_name: '',
full_name: '',
tax_number: '',
social_security_number: '',
description: '',
address: Address,
phone: Phone
}
const formCreateCase = useForm({
client_uuid: props.clientUuid,
person: Person
});
const storeCase = () => {
formCreateCase.post(route('clientCase.store'), {
onSuccess: () => {
close();
formCreateCase.reset();
}
});
};
</script>
<template>
<Drawer
:show="show"
@close="close">
<template #title>Nova primer</template>
<template #content>
<form @submit.prevent="storeCase">
<SectionTitle class="border-b mb-4">
<template #title>
Oseba
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="fullname" value="Naziv"/>
<TextInput
id="fullname"
ref="fullnameInput"
v-model="formCreateCase.person.full_name"
type="text"
class="mt-1 block w-full"
autocomplete="full-name"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="taxnumber" value="Davčna"/>
<TextInput
id="taxnumber"
ref="taxnumberInput"
v-model="formCreateCase.tax_number"
type="text"
class="mt-1 block w-full"
autocomplete="tax-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="socialSecurityNumber" value="Matična / Emšo"/>
<TextInput
id="socialSecurityNumber"
ref="socialSecurityNumberInput"
v-model="formCreateCase.social_security_number"
type="text"
class="mt-1 block w-full"
autocomplete="social-security-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="address" value="Naslov"/>
<TextInput
id="address"
ref="addressInput"
v-model="formCreateCase.person.address.address"
type="text"
class="mt-1 block w-full"
autocomplete="address"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="addressCountry" value="Država"/>
<TextInput
id="addressCountry"
ref="addressCountryInput"
v-model="formCreateCase.person.address.country"
type="text"
class="mt-1 block w-full"
autocomplete="address-country"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="addressType" value="Vrsta naslova"/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="addressType"
v-model="formCreateCase.person.address.type_id"
>
<option value="1">Stalni</option>
<option value="2">Začasni</option>
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="phoneCountyCode" value="Koda države tel."/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="phoneCountyCode"
v-model="formCreateCase.person.phone.country_code"
>
<option value="00386">+386 (Slovenija)</option>
<option value="00385">+385 (Hrvaška)</option>
<option value="0039">+39 (Italija)</option>
<option value="0036">+39 (Madžarska)</option>
<option value="0043">+43 (Avstrija)</option>
<option value="00381">+381 (Srbija)</option>
<option value="00387">+387 (Bosna in Hercegovina)</option>
<option value="00382">+382 (Črna gora)</option>
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="phoneNu" value="Telefonska št."/>
<TextInput
id="phoneNu"
ref="phoneNuInput"
v-model="formCreateCase.person.phone.nu"
type="text"
class="mt-1 block w-full"
autocomplete="phone-nu"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="description" value="Opis"/>
<TextInput
id="description"
ref="descriptionInput"
v-model="formCreateCase.description"
type="text"
class="mt-1 block w-full"
autocomplete="description"
/>
</div>
<div class="flex justify-end mt-4">
<ActionMessage :on="formCreateCase.recentlySuccessful" class="me-3">
Shranjeno.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': formCreateCase.processing }" :disabled="formCreateCase.processing">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -0,0 +1,118 @@
<script setup>
import ActionMessage from '@/Components/ActionMessage.vue';
import Drawer from '@/Components/Drawer.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SectionTitle from '@/Components/SectionTitle.vue';
import TextInput from '@/Components/TextInput.vue';
import { useForm } from '@inertiajs/vue3';
import { onMounted } from 'vue';
const props = defineProps({
show: {
type: Boolean,
default: false
},
client: Object
});
const emit = defineEmits(['close']);
const close = () => {
emit('close');
}
const form = useForm({
person: props.client.person
});
const updateClient = () => {
form.put(route('person.update', props.client.person ), {
onSuccess: () => {
close();
form.reset();
}
});
}
onMounted(() => {
console.log(props.client)
});
</script>
<template>
<Drawer
:show="show"
@close="close"
>
<template #title>Posodobi {{ client.person.full_name }}</template>
<template #content>
<form @submit.prevent="updateClient">
<SectionTitle class="border-b mb-4">
<template #title>
Oseba
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cfulname" value="Naziv" />
<TextInput
id="cfullname"
ref="cfullnameInput"
v-model="form.person.full_name"
type="text"
class="mt-1 block w-full"
autocomplete="full-name"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="ctaxnumber" value="Davčna" />
<TextInput
id="ctaxnumber"
ref="ctaxnumberInput"
v-model="form.person.tax_number"
type="text"
class="mt-1 block w-full"
autocomplete="tax-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="csocialSecurityNumber" value="Matična / Emšo" />
<TextInput
id="csocialSecurityNumber"
ref="csocialSecurityNumberInput"
v-model="form.person.social_security_number"
type="text"
class="mt-1 block w-full"
autocomplete="social-security-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="cdescription" value="Opis"/>
<TextInput
id="cdescription"
ref="cdescriptionInput"
v-model="form.description"
type="text"
class="mt-1 block w-full"
autocomplete="description"
/>
</div>
<div class="flex justify-end mt-4">
<ActionMessage :on="form.recentlySuccessful" class="me-3">
Shranjeno.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -3,52 +3,20 @@ import AppLayout from '@/Layouts/AppLayout.vue';
import List from '@/Components/List.vue';
import ListItem from '@/Components/ListItem.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import Drawer from '@/Components/Drawer.vue';
import InputLabel from '@/Components/InputLabel.vue';
import TextInput from '@/Components/TextInput.vue';
import { ref } from 'vue';
import { Link, useForm } from '@inertiajs/vue3';
import ActionMessage from '@/Components/ActionMessage.vue';
import { Link } from '@inertiajs/vue3';
import SectionTitle from '@/Components/SectionTitle.vue';
import PersonInfoGrid from '@/Components/PersonInfoGrid.vue';
import Pagination from '@/Components/Pagination.vue';
import SearchInput from '@/Components/SearchInput.vue';
import FormCreateCase from './Partials/FormCreateCase.vue';
const props = defineProps({
client: Object,
client_cases: Object,
urlPrev: String,
filters: Object
});
console.log(props.client_cases)
const Address = {
address: '',
country: '',
type_id: 1
}
const Phone = {
nu: '',
country_code: '00386',
type_id: 1
}
const Person = {
first_name: '',
last_name: '',
full_name: '',
tax_number: '',
social_security_number: '',
description: '',
address: Address,
phone: Phone
}
const formCreateCase = useForm({
client_uuid: props.client.uuid,
person: Person
filters: Object,
types: Object
});
const search = {
@ -65,42 +33,6 @@ const openDrawerCreateCase = () => {
drawerCreateCase.value = true;
}
const closeDrawer = () => {
drawerCreateCase.value = false
}
const getMainAddress = (adresses) => {
const addr = adresses.filter( a => a.type.id === 1 )[0] ?? '';
const country = addr.country !== '' ? ` - ${addr.country}` : '';
return addr.address !== '' ? addr.address + country : '';
}
const getMainPhone = (phones) => {
const pho = phones.filter( a => a.type.id === 1 )[0] ?? '';
const countryCode = pho.country_code !== null ? `+${pho.country_code} ` : '';
return pho.nu !== '' ? countryCode + pho.nu: '';
}
const clientInfo = new Object({
nu: props.client.person.nu,
full_name: props.client.person.full_name,
main_address: getMainAddress(props.client.person.addresses),
main_phone: getMainPhone(props.client.person.phones),
tax_number: props.client.person.tax_number,
social_security_number: props.client.person.social_security_number,
description: props.client.person.description
});
const storeCase = () => {
formCreateCase.post(route('clientCase.store'), {
onSuccess: () => {
closeDrawer();
formCreateCase.reset();
console.log(props.client_cases)
}
});
};
</script>
<template>
@ -109,12 +41,11 @@ const storeCase = () => {
<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 border-l-4 border-blue-400">
<div class="mx-auto max-w-4x1 p-3">
<div class="mx-auto max-w-4x1 p-3 flex justify-between">
<SectionTitle>
<template #title>
{{ client.person.full_name }}
</template>
</SectionTitle>
</div>
</div>
@ -123,8 +54,8 @@ const storeCase = () => {
<div class="pt-1">
<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="mx-auto max-w-4x1 p-3">
<PersonInfoGrid :person="clientInfo"></PersonInfoGrid>
<div class="mx-auto max-w-4x1 px-2">
<PersonInfoGrid :types="types" :person="client.person"></PersonInfoGrid>
</div>
</div>
</div>
@ -162,141 +93,9 @@ const storeCase = () => {
</div>
</div>
</AppLayout>
<Drawer
:show="drawerCreateCase"
@close="drawerCreateCase = false">
<template #title>Nova primer</template>
<template #content>
<form @submit.prevent="storeCase">
<SectionTitle class="border-b mb-4">
<template #title>
Oseba
</template>
</SectionTitle>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="fullname" value="Naziv"/>
<TextInput
id="fullname"
ref="fullnameInput"
v-model="formCreateCase.person.full_name"
type="text"
class="mt-1 block w-full"
autocomplete="full-name"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="taxnumber" value="Davčna"/>
<TextInput
id="taxnumber"
ref="taxnumberInput"
v-model="formCreateCase.tax_number"
type="text"
class="mt-1 block w-full"
autocomplete="tax-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="socialSecurityNumber" value="Matična / Emšo"/>
<TextInput
id="socialSecurityNumber"
ref="socialSecurityNumberInput"
v-model="formCreateCase.social_security_number"
type="text"
class="mt-1 block w-full"
autocomplete="social-security-number"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="address" value="Naslov"/>
<TextInput
id="address"
ref="addressInput"
v-model="formCreateCase.person.address.address"
type="text"
class="mt-1 block w-full"
autocomplete="address"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="addressCountry" value="Država"/>
<TextInput
id="addressCountry"
ref="addressCountryInput"
v-model="formCreateCase.person.address.country"
type="text"
class="mt-1 block w-full"
autocomplete="address-country"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="addressType" value="Vrsta naslova"/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="addressType"
v-model="formCreateCase.person.address.type_id"
>
<option value="1">Stalni</option>
<option value="2">Začasni</option>
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="phoneCountyCode" value="Koda države tel."/>
<select
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm"
id="phoneCountyCode"
v-model="formCreateCase.person.phone.country_code"
>
<option value="00386">+386 (Slovenija)</option>
<option value="00385">+385 (Hrvaška)</option>
<option value="0039">+39 (Italija)</option>
<option value="0036">+39 (Madžarska)</option>
<option value="0043">+43 (Avstrija)</option>
<option value="00381">+381 (Srbija)</option>
<option value="00387">+387 (Bosna in Hercegovina)</option>
<option value="00382">+382 (Črna gora)</option>
<!-- ... -->
</select>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="phoneNu" value="Telefonska št."/>
<TextInput
id="phoneNu"
ref="phoneNuInput"
v-model="formCreateCase.person.phone.nu"
type="text"
class="mt-1 block w-full"
autocomplete="phone-nu"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="description" value="Opis"/>
<TextInput
id="description"
ref="descriptionInput"
v-model="formCreateCase.description"
type="text"
class="mt-1 block w-full"
autocomplete="description"
/>
</div>
<div class="flex justify-end mt-4">
<ActionMessage :on="formCreateCase.recentlySuccessful" class="me-3">
Shranjeno.
</ActionMessage>
<PrimaryButton :class="{ 'opacity-25': formCreateCase.processing }" :disabled="formCreateCase.processing">
Shrani
</PrimaryButton>
</div>
</form>
</template>
</Drawer>
<FormCreateCase
:show="drawerCreateCase"
@close="drawerCreateCase = false"
:client-uuid="client?.uuid"
/>
</template>

View File

@ -6,10 +6,12 @@ import { LinkOptions as C_LINK, TableColumn as C_TD, TableRow as C_TR} from '@/S
const props = defineProps({
chart: Object,
people: Array
people: Array,
terrain: Array
});
console.log(props.people)
console.log(props.terrain)
const tablePersonHeader = [
@ -18,14 +20,21 @@ const tablePersonHeader = [
C_TD.make('Skupina', 'header')
];
let tablePersonBody = [];
const tblTerrainHead = [
C_TD.make('Št.', 'header'),
C_TD.make('Naziv', 'header'),
C_TD.make('Začetek', 'header')
];
let tablePersonBody = [];
let tblTerrainBody = [];
const getRoute = (person) => {
if( person.client ){
return {route: 'client.show', options: person.client};
}
return {route: 'clientCase.show', options: person.client_case};
return {route: 'clientCase.show', options: person};
}
props.people.forEach((p) => {
@ -34,12 +43,25 @@ props.people.forEach((p) => {
const cols = [
C_TD.make(Number(p.nu), 'body', {}, C_LINK.make(forLink.route, forLink.options, `font-bold hover:text-${p.group.color_tag}`)),
C_TD.make(p.full_name, 'body'),
C_TD.make(p.group.name)
C_TD.make(p.group.added_segment, 'body')
];
tablePersonBody.push(C_TR.make(cols, {class: `border-l-4 border-${p.group.color_tag}`}))
});
props.terrain.forEach((t) => {
const forLink = getRoute(t);
const startDate = new Date(t.added_segment).toLocaleDateString('de');
const cols = [
C_TD.make(t.person.nu, 'body', {}, C_LINK.make(forLink.route, forLink.options, `font-bold hover:text-red-400`)),
C_TD.make(t.person.full_name, 'body'),
C_TD.make(startDate, 'body')
];
tblTerrainBody.push(C_TR.make(cols, {class: `border-l-4 border-red-400`}));
});
</script>
<template>
@ -67,7 +89,7 @@ props.people.forEach((p) => {
Seznam primerov za terensko delo
</template>
</SectionTitle>
<BasicTable :header="tablePersonHeader" :body="tablePersonBody"></BasicTable>
<BasicTable :header="tblTerrainHead" :body="tblTerrainBody"></BasicTable>
</div>
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<SectionTitle class="p-4">

View File

@ -21,7 +21,7 @@ const activeTab = ref('actions')
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<fwb-tabs v-model="activeTab" variant="underline" >
<fwb-tab name="actions" title="Actions">
<ActionTable :actions="actions" />
<ActionTable :actions="actions" :decisions="decisions" />
</fwb-tab>
<fwb-tab name="decisions" title="Decisions">
<DecisionTable :decisions="decisions" />

View File

@ -1,12 +1,69 @@
<script setup>
import { FwbTable, FwbTableBody, FwbTableHead, FwbTableHeadCell, FwbTableCell, FwbTableRow } from 'flowbite-vue';
import { EditIcon, TrashBinIcon } from '@/Utilities/Icons';
import Drawer from '@/Components/Drawer.vue';
import { onMounted, ref } from 'vue';
import { useForm } from '@inertiajs/vue3';
import InputLabel from '@/Components/InputLabel.vue';
import TextInput from '@/Components/TextInput.vue';
import Multiselect from 'vue-multiselect';
const props = defineProps({
actions: Array
actions: Array,
decisions: Array
});
const drawerEdit = ref(false);
const selectOptions = ref([]);
const selectValue = ref([]);
const form = useForm({
id: 0,
name: '',
color_tag: '',
decisions: []
});
const openEditDrawer = (item) => {
form.id = item.id;
form.name = item.name;
form.color_tag = item.color_tag;
drawerEdit.value = true;
console.log(item.decisions);
item.decisions.forEach((d) => {
selectValue.value.push({
name: d.name,
code: d.name.substring(0,2).toLowerCase() + d.id
});
})
}
const closeEditDrawer = () => {
form.reset();
drawerEdit.value = false;
}
const onColorPickerChange = () => {
console.log(form.color_tag);
}
onMounted(() => {
props.decisions.forEach((d) => {
selectOptions.value.push({
name: d.name,
code: d.name.substring(0,2).toLowerCase() + d.id
});
});
console.log(selectOptions.value);
});
</script>
<template>
@ -27,10 +84,63 @@ const props = defineProps({
<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>
<button @click="openEditDrawer(act)"><EditIcon /></button>
</fwb-table-cell>
</fwb-table-row>
</fwb-table-body>
</fwb-table>
<Drawer
:show="drawerEdit"
@close="closeEditDrawer"
>
<template #title>
<span>Spremeni akcijo</span>
</template>
<template #content>
<form @submit.prevent="">
<div class="col-span-6 sm:col-span-4">
<InputLabel for="name" value="Ime"/>
<TextInput
id="name"
ref="nameInput"
v-model="form.name"
type="text"
class="mt-1 block w-full"
autocomplete="name"
/>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="colorTag" value="Barva"/>
<div class="mt-1 w-full border flex p-1">
<TextInput
id="colorTag"
ref="colorTagInput"
v-model="form.color_tag"
@change="onColorPickerChange"
type="color"
class="mr-2"
autocomplete="color-tag"
/>
<span>{{ form.color_tag }}</span>
</div>
</div>
<div class="col-span-6 sm:col-span-4">
<InputLabel for="decisions" value="Odločitve"/>
<multiselect
id="decisions"
ref="decisionsSelect"
v-model="selectValue"
:options="selectOptions"
:multiple="true"
track-by="code"
:taggable="true"
placeholder="Dodaj odločitev"
label="name"
/>
</div>
</form>
</template>
</Drawer>
</template>

View File

@ -1,13 +1,37 @@
import { ref } from "vue";
const Icon = {
props: {
css: {
type: String,
default: 'text-gray-800'
},
size: {
type: String,
default: 'md'
}
}
},
methods: {
defineSize: (val) => {
let size = '';
switch(val){
case 'xs':
size = 'w-3 h-3';
break;
case 'sm':
size = 'w-4 h-4';
break;
case 'lg':
size = 'w-6 h-6';
break;
default:
size = 'w-5 h-5';
break;
}
return size;
}
},
}
const AddressBookIcon = {
@ -15,52 +39,98 @@ const AddressBookIcon = {
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">
template: `<svg class="dark:text-white" :class="[defineSize(size),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">
template: `<svg class=" 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">
template: `<svg class="dark:text-white" :class="[defineSize(size),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">
template: `<svg class=" dark:text-white" :class="[defineSize(size),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">
template: `<svg aria-hidden="true" class="text-gray-500 dark:text-gray-400" :class="[defineSize(size),css]" fill="none" stroke="currentColor" width="24" height="24" 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">
template: `<svg class="text-gray-500 dark:text-white" :class="[defineSize(size),css]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" :class="[defineSize(size),css]" 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>
`
}
const AngleDownIcon = {
__proto__: Icon,
template: `<svg class="text-gray-500 dark:text-white" :class="[defineSize(size),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="m19 9-7 7-7-7"/>
</svg>
`
}
const AngleUpIcon = {
__proto__: Icon,
template: `<svg class="text-gray-800 dark:text-white" :class="[defineSize(size),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="m5 15 7-7 7 7"/>
</svg>
`
}
const UserEditIcon = {
__proto__: Icon,
template: `<svg class="dark:text-white" :class="[defineSize(size),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="square" stroke-linejoin="round" stroke-width="2" d="M7 19H5a1 1 0 0 1-1-1v-1a3 3 0 0 1 3-3h1m4-6a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm7.441 1.559a1.907 1.907 0 0 1 0 2.698l-6.069 6.069L10 19l.674-3.372 6.07-6.07a1.907 1.907 0 0 1 2.697 0Z"/>
</svg>
`
}
const CirclePlusIcon = {
__proto__: Icon,
template: `<svg class="dark:text-white" :class="[defineSize(size),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="M12 7.757v8.486M7.757 12h8.486M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
`
}
const PlusIcon = {
__proto__: Icon,
template: `<svg class="dark:text-white" :class="[defineSize(size),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="M5 12h14m-7 7V5"/>
</svg>
`
}
export {
AddressBookIcon,
AlignCenterIcon,
EditIcon,
TrashBinIcon,
SearchIcon,
AdjustmentIcon
AdjustmentIcon,
AngleDownIcon,
AngleUpIcon,
UserEditIcon,
CirclePlusIcon,
PlusIcon
};

View File

@ -7,7 +7,7 @@ import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import VueApexCharts from 'vue3-apexcharts';
import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'
import '@vuepic/vue-datepicker/dist/main.css';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';

View File

@ -1,6 +1,7 @@
<?php
use App\Charts\ExampleChart;
use App\Http\Controllers\PersonController;
use App\Http\Controllers\ClientCaseContoller;
use App\Http\Controllers\ClientController;
use App\Http\Controllers\ContractController;
@ -21,7 +22,7 @@
])->group(function () {
Route::get('/dashboard', function () {
$chart = new ExampleChart(new LarapexChart());
$people = Person::with(['group','type', 'client', 'clientCase'])
$people = Person::with(['group', 'type', 'client', 'clientCase'])
->where([
['active','=',1],
])
@ -29,11 +30,21 @@
->orderByDesc('created_at')
->get();
$terrain = \App\Models\ClientCase::join('client_case_segment', 'client_cases.id', '=', 'client_case_segment.client_case_id')
->select('client_cases.*', 'client_case_segment.created_at as added_segment')
->where('client_case_segment.segment_id', '=', 2)
->where('client_case_segment.active', '=', true)
->orderByDesc('client_case_segment.created_at')
->limit(10)
->with('person')
->get();
return Inertia::render(
'Dashboard',
[
'chart' => $chart->build(),
'people' => $people
'people' => $people,
'terrain' => $terrain
]
);
})->name('dashboard');
@ -45,24 +56,6 @@
Route::get('search', function(Request $request) {
if( !empty($request->input('query')) ) {
/*$clients = App\Models\Client::search($request->input('query'))
->query( function($builder) use($request){
$builder->leftJoin('person', 'clients.person_id', '=', 'person.id')
->leftJoin('person_addresses', 'person_addresses.person_id', '=', 'person.id')
->select('clients.*', 'person.full_name as person_fullname')
->limit($request->input('limit'));
})
->get();
$clientCases = App\Models\ClientCase::search($request->input('query'))
->query(function($builder) use($request){
$builder->join('person', 'client_cases.person_id', '=', 'person.id')
->select('client_cases.*', 'person.full_name as person_fullname')
->limit($request->input('limit'));
})
->get();*/
$clients = App\Models\Person\Person::search($request->input('query'))
->query(function($builder) use($request): void {
@ -95,10 +88,17 @@
return [];
})->name('search');
//person
Route::put('person/{person:uuid}', [PersonController::class, 'update'])->name('person.update');
Route::post('person/{person:uuid}/address', [PersonController::class, 'createAddress'])->name('person.address.create');
Route::put('person/{person:uuid}/address/{address_id}', [PersonController::class, 'updateAddress'])->name('person.address.update');
Route::post('person/{person:uuid}/phone', [PersonController::class, 'createPhone'])->name('person.phone.create');
Route::put('person/{person:uuid}/phone/{phone_id}', [PersonController::class, 'updatePhone'])->name('person.phone.update');
//client
Route::get('clients', [ClientController::class, 'index'])->name('client');
Route::get('clients/{client:uuid}', [ClientController::class, 'show'])->name('client.show');
Route::post('clients', [ClientController::class, 'store'])->name('client.store');
Route::put('clients/{client:uuid}', [ClientController::class, 'update'])->name('client.update');
//client-case
Route::get('client-cases', [ClientCaseContoller::class, 'index'])->name('clientCase');
@ -113,4 +113,14 @@
Route::get('settings', [SettingController::class, 'index'])->name('settings');
//types
Route::get('types/address', function(Request $request){
$types = App\Models\Person\AddressType::all();
return response()->json([
'types' => $types
]);
})->name('types.address');
});