changes to sms packages and option to create user
This commit is contained in:
@@ -35,6 +35,15 @@ const filteredSenders = computed(() => {
|
||||
return props.senders.filter(s => s.profile_id === form.profile_id)
|
||||
})
|
||||
|
||||
function onTemplateChange() {
|
||||
const template = props.templates.find(t => t.id === form.template_id)
|
||||
if (template?.content) {
|
||||
form.body = template.content
|
||||
} else {
|
||||
form.body = ''
|
||||
}
|
||||
}
|
||||
|
||||
function submitCreate() {
|
||||
const lines = (form.numbers || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean)
|
||||
if (!lines.length) return
|
||||
@@ -78,6 +87,8 @@ const contracts = ref({ data: [], meta: { current_page: 1, last_page: 1, per_pag
|
||||
const segmentId = ref(null)
|
||||
const search = ref('')
|
||||
const clientId = ref(null)
|
||||
const startDateFrom = ref('')
|
||||
const startDateTo = ref('')
|
||||
const onlyMobile = ref(false)
|
||||
const onlyValidated = ref(false)
|
||||
const loadingContracts = ref(false)
|
||||
@@ -90,7 +101,7 @@ async function loadContracts(url = null) {
|
||||
}
|
||||
loadingContracts.value = true
|
||||
try {
|
||||
const target = url || `${route('admin.packages.contracts')}?segment_id=${encodeURIComponent(segmentId.value)}${search.value ? `&q=${encodeURIComponent(search.value)}` : ''}${clientId.value ? `&client_id=${encodeURIComponent(clientId.value)}` : ''}${onlyMobile.value ? `&only_mobile=1` : ''}${onlyValidated.value ? `&only_validated=1` : ''}`
|
||||
const target = url || `${route('admin.packages.contracts')}?segment_id=${encodeURIComponent(segmentId.value)}${search.value ? `&q=${encodeURIComponent(search.value)}` : ''}${clientId.value ? `&client_id=${encodeURIComponent(clientId.value)}` : ''}${startDateFrom.value ? `&start_date_from=${encodeURIComponent(startDateFrom.value)}` : ''}${startDateTo.value ? `&start_date_to=${encodeURIComponent(startDateTo.value)}` : ''}${onlyMobile.value ? `&only_mobile=1` : ''}${onlyValidated.value ? `&only_validated=1` : ''}`
|
||||
const res = await fetch(target, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
||||
const json = await res.json()
|
||||
contracts.value = { data: json.data || [], meta: json.meta || { current_page: 1, last_page: 1, per_page: 25, total: 0 } }
|
||||
@@ -110,11 +121,37 @@ function clearSelection() {
|
||||
selectedContractIds.value = new Set()
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
const currentPageIds = contracts.value.data.map(c => c.id)
|
||||
const allSelected = currentPageIds.every(id => selectedContractIds.value.has(id))
|
||||
|
||||
if (allSelected) {
|
||||
// Deselect all on current page
|
||||
currentPageIds.forEach(id => selectedContractIds.value.delete(id))
|
||||
} else {
|
||||
// Select all on current page
|
||||
currentPageIds.forEach(id => selectedContractIds.value.add(id))
|
||||
}
|
||||
|
||||
// Force reactivity
|
||||
selectedContractIds.value = new Set(Array.from(selectedContractIds.value))
|
||||
}
|
||||
|
||||
const allCurrentPageSelected = computed(() => {
|
||||
if (!contracts.value.data.length) return false
|
||||
return contracts.value.data.every(c => selectedContractIds.value.has(c.id))
|
||||
})
|
||||
|
||||
const someCurrentPageSelected = computed(() => {
|
||||
if (!contracts.value.data.length) return false
|
||||
return contracts.value.data.some(c => selectedContractIds.value.has(c.id)) && !allCurrentPageSelected.value
|
||||
})
|
||||
|
||||
function goContractsPage(delta) {
|
||||
const { current_page } = contracts.value.meta
|
||||
const nextPage = current_page + delta
|
||||
if (nextPage < 1 || nextPage > contracts.value.meta.last_page) return
|
||||
const base = `${route('admin.packages.contracts')}?segment_id=${encodeURIComponent(segmentId.value)}${search.value ? `&q=${encodeURIComponent(search.value)}` : ''}${clientId.value ? `&client_id=${encodeURIComponent(clientId.value)}` : ''}${onlyMobile.value ? `&only_mobile=1` : ''}${onlyValidated.value ? `&only_validated=1` : ''}&page=${nextPage}`
|
||||
const base = `${route('admin.packages.contracts')}?segment_id=${encodeURIComponent(segmentId.value)}${search.value ? `&q=${encodeURIComponent(search.value)}` : ''}${clientId.value ? `&client_id=${encodeURIComponent(clientId.value)}` : ''}${startDateFrom.value ? `&start_date_from=${encodeURIComponent(startDateFrom.value)}` : ''}${startDateTo.value ? `&start_date_to=${encodeURIComponent(startDateTo.value)}` : ''}${onlyMobile.value ? `&only_mobile=1` : ''}${onlyValidated.value ? `&only_validated=1` : ''}&page=${nextPage}`
|
||||
loadContracts(base)
|
||||
}
|
||||
|
||||
@@ -179,7 +216,7 @@ function submitCreateFromContracts() {
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Predloga</label>
|
||||
<select v-model.number="form.template_id" class="w-full rounded border-gray-300 text-sm">
|
||||
<select v-model.number="form.template_id" @change="onTemplateChange" class="w-full rounded border-gray-300 text-sm">
|
||||
<option :value="null">—</option>
|
||||
<option v-for="t in templates" :key="t.id" :value="t.id">{{ t.name }}</option>
|
||||
</select>
|
||||
@@ -220,12 +257,20 @@ function submitCreateFromContracts() {
|
||||
<option v-for="c in clients" :key="c.id" :value="c.id">{{ c.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Iskanje</label>
|
||||
<div class="flex gap-2">
|
||||
<input v-model="search" @keyup.enter="loadContracts()" type="text" class="w-full rounded border-gray-300 text-sm" placeholder="referenca...">
|
||||
<button @click="loadContracts()" class="px-3 py-1.5 rounded border text-sm">Išči</button>
|
||||
</div>
|
||||
<input v-model="search" @keyup.enter="loadContracts()" type="text" class="w-full rounded border-gray-300 text-sm" placeholder="referenca...">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Datum začetka od</label>
|
||||
<input v-model="startDateFrom" @change="loadContracts()" type="date" class="w-full rounded border-gray-300 text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-gray-500 mb-1">Datum začetka do</label>
|
||||
<input v-model="startDateTo" @change="loadContracts()" type="date" class="w-full rounded border-gray-300 text-sm">
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button @click="loadContracts()" class="px-3 py-1.5 rounded border text-sm h-fit">Išči</button>
|
||||
</div>
|
||||
<div class="sm:col-span-3 flex items-center gap-6 text-sm text-gray-700">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
@@ -240,10 +285,21 @@ function submitCreateFromContracts() {
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr class="text-xs text-gray-500">
|
||||
<th class="px-3 py-2"></th>
|
||||
<th class="px-3 py-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="allCurrentPageSelected"
|
||||
:indeterminate="someCurrentPageSelected"
|
||||
@change="toggleSelectAll"
|
||||
:disabled="!contracts.data.length"
|
||||
class="rounded"
|
||||
title="Izberi vse na tej strani"
|
||||
>
|
||||
</th>
|
||||
<th class="px-3 py-2 text-left">Pogodba</th>
|
||||
<th class="px-3 py-2 text-left">Primer</th>
|
||||
<th class="px-3 py-2 text-left">Stranka</th>
|
||||
<th class="px-3 py-2 text-left">Datum začetka</th>
|
||||
<th class="px-3 py-2 text-left">Izbrana številka</th>
|
||||
<th class="px-3 py-2 text-left">Opomba</th>
|
||||
</tr>
|
||||
@@ -263,6 +319,9 @@ function submitCreateFromContracts() {
|
||||
<td class="px-3 py-2">
|
||||
<div class="text-xs text-gray-800">{{ c.client?.name || '—' }}</div>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<div class="text-xs text-gray-700">{{ c.start_date ? new Date(c.start_date).toLocaleDateString('sl-SI') : '—' }}</div>
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
<div v-if="c.selected_phone" class="text-xs">
|
||||
{{ c.selected_phone.number }}
|
||||
@@ -273,11 +332,11 @@ function submitCreateFromContracts() {
|
||||
<td class="px-3 py-2 text-xs text-gray-500">{{ c.no_phone_reason || '—' }}</td>
|
||||
</tr>
|
||||
<tr v-if="!contracts.data?.length">
|
||||
<td colspan="6" class="px-3 py-8 text-center text-sm text-gray-500">Ni rezultatov.</td>
|
||||
<td colspan="7" class="px-3 py-8 text-center text-sm text-gray-500">Ni rezultatov.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else>
|
||||
<tr><td colspan="6" class="px-3 py-6 text-center text-sm text-gray-500">Nalaganje...</td></tr>
|
||||
<tr><td colspan="7" class="px-3 py-6 text-center text-sm text-gray-500">Nalaganje...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@ import AdminLayout from "@/Layouts/AdminLayout.vue";
|
||||
import { useForm, Link } from "@inertiajs/vue3";
|
||||
import { ref, computed } from "vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { faMagnifyingGlass, faFloppyDisk } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faMagnifyingGlass, faFloppyDisk, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import DialogModal from "@/Components/DialogModal.vue";
|
||||
|
||||
const props = defineProps({
|
||||
users: Array,
|
||||
@@ -65,6 +66,43 @@ const filteredUsers = computed(() => {
|
||||
});
|
||||
|
||||
const anyDirty = computed(() => Object.values(forms).some((f) => f.dirty));
|
||||
|
||||
// Create user modal
|
||||
const showCreateModal = ref(false);
|
||||
const createForm = useForm({
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
password_confirmation: "",
|
||||
roles: [],
|
||||
});
|
||||
|
||||
function openCreateModal() {
|
||||
createForm.reset();
|
||||
createForm.clearErrors();
|
||||
showCreateModal.value = true;
|
||||
}
|
||||
|
||||
function closeCreateModal() {
|
||||
showCreateModal.value = false;
|
||||
createForm.reset();
|
||||
}
|
||||
|
||||
function submitCreateUser() {
|
||||
createForm.post(route("admin.users.store"), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
closeCreateModal();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCreateRole(roleId) {
|
||||
const exists = createForm.roles.includes(roleId);
|
||||
createForm.roles = exists
|
||||
? createForm.roles.filter((id) => id !== roleId)
|
||||
: [...createForm.roles, roleId];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -127,6 +165,14 @@ const anyDirty = computed(() => Object.values(forms).some((f) => f.dirty));
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="openCreateModal"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-md text-xs font-medium bg-indigo-600 border-indigo-600 text-white hover:bg-indigo-500"
|
||||
>
|
||||
<FontAwesomeIcon :icon="faPlus" class="w-4 h-4" />
|
||||
Ustvari uporabnika
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="submitAll"
|
||||
@@ -265,5 +311,107 @@ const anyDirty = computed(() => Object.values(forms).some((f) => f.dirty));
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create User Modal -->
|
||||
<DialogModal :show="showCreateModal" @close="closeCreateModal" max-width="2xl">
|
||||
<template #title>Ustvari novega uporabnika</template>
|
||||
<template #content>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Ime</label>
|
||||
<input
|
||||
v-model="createForm.name"
|
||||
type="text"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="Ime uporabnika"
|
||||
/>
|
||||
<div v-if="createForm.errors.name" class="text-red-600 text-xs mt-1">
|
||||
{{ createForm.errors.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">E-pošta</label>
|
||||
<input
|
||||
v-model="createForm.email"
|
||||
type="email"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="uporabnik@example.com"
|
||||
/>
|
||||
<div v-if="createForm.errors.email" class="text-red-600 text-xs mt-1">
|
||||
{{ createForm.errors.email }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Geslo</label>
|
||||
<input
|
||||
v-model="createForm.password"
|
||||
type="password"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="********"
|
||||
/>
|
||||
<div v-if="createForm.errors.password" class="text-red-600 text-xs mt-1">
|
||||
{{ createForm.errors.password }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Potrdi geslo</label>
|
||||
<input
|
||||
v-model="createForm.password_confirmation"
|
||||
type="password"
|
||||
class="w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="********"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Vloge</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<label
|
||||
v-for="role in props.roles"
|
||||
:key="'create-role-' + role.id"
|
||||
class="inline-flex items-center gap-2 px-3 py-2 rounded-md border cursor-pointer transition"
|
||||
:class="
|
||||
createForm.roles.includes(role.id)
|
||||
? 'bg-indigo-50 border-indigo-600 text-indigo-700'
|
||||
: 'bg-white border-gray-300 text-gray-700 hover:bg-gray-50'
|
||||
"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
||||
:checked="createForm.roles.includes(role.id)"
|
||||
@change="toggleCreateRole(role.id)"
|
||||
/>
|
||||
<span class="text-sm font-medium">{{ role.name }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="createForm.errors.roles" class="text-red-600 text-xs mt-1">
|
||||
{{ createForm.errors.roles }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
type="button"
|
||||
@click="closeCreateModal"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium bg-white border border-gray-300 text-gray-700 hover:bg-gray-50"
|
||||
>
|
||||
Prekliči
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="submitCreateUser"
|
||||
:disabled="createForm.processing"
|
||||
class="ml-3 px-4 py-2 rounded-md text-sm font-medium bg-indigo-600 text-white hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span v-if="createForm.processing">Ustvarjanje...</span>
|
||||
<span v-else>Ustvari</span>
|
||||
</button>
|
||||
</template>
|
||||
</DialogModal>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
|
||||
@@ -632,17 +632,15 @@ const closePaymentsDialog = () => {
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="max-w-sm px-3 py-2 text-sm text-gray-700">
|
||||
<div class="min-w-[200px] max-w-xs px-3 py-2 text-sm text-gray-700">
|
||||
<template v-if="hasMeta(c)">
|
||||
<div
|
||||
v-for="(m, idx) in getMetaEntries(c)"
|
||||
:key="idx"
|
||||
class="flex items-start gap-2 py-0.5"
|
||||
class="py-1"
|
||||
>
|
||||
<span class="text-gray-500 whitespace-nowrap"
|
||||
>{{ m.title }}:</span
|
||||
>
|
||||
<span class="text-gray-800">{{ formatMetaValue(m) }}</span>
|
||||
<div class="text-gray-500 text-xs mb-0.5">{{ m.title }}</div>
|
||||
<div class="text-gray-800 font-medium break-all">{{ formatMetaValue(m) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -131,6 +131,7 @@ watch(
|
||||
{ value: 'emails', label: 'Emails' },
|
||||
{ value: 'accounts', label: 'Accounts' },
|
||||
{ value: 'contracts', label: 'Contracts' },
|
||||
{ value: 'case_objects', label: 'Case Objects' },
|
||||
{ value: 'payments', label: 'Payments' },
|
||||
]"
|
||||
:multiple="true"
|
||||
|
||||
Reference in New Issue
Block a user