Compare commits
2 Commits
11df4ef55c
...
06e5600447
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06e5600447 | ||
|
|
ed16d5dcda |
@ -52,17 +52,7 @@ class DynamicController extends Controller
|
|||||||
|
|
||||||
$activityService->updateCursor($request->user(), $dynamic);
|
$activityService->updateCursor($request->user(), $dynamic);
|
||||||
|
|
||||||
$dynamic->load([
|
$dynamic->load(['ledgers.media', 'participants', 'chat']);
|
||||||
'ledgers.media',
|
|
||||||
'participants' => fn($query) => $query->withPivot('display_name'),
|
|
||||||
'chat.messages.user',
|
|
||||||
'chat.messages.media',
|
|
||||||
'chat.messages.subject' => function ($morphTo) {
|
|
||||||
$morphTo->morphWith([
|
|
||||||
\App\Models\Mutation::class => ['ledger'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
$isOwner = $dynamic->participants()
|
$isOwner = $dynamic->participants()
|
||||||
->where('user_id', $request->user()->id)
|
->where('user_id', $request->user()->id)
|
||||||
@ -72,9 +62,17 @@ class DynamicController extends Controller
|
|||||||
return Inertia::render('Dynamics/Show', [
|
return Inertia::render('Dynamics/Show', [
|
||||||
'dynamic' => $dynamic,
|
'dynamic' => $dynamic,
|
||||||
'isOwner' => $isOwner,
|
'isOwner' => $isOwner,
|
||||||
|
'messages' => $dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(20),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function messages(Request $request, Dynamic $dynamic)
|
||||||
|
{
|
||||||
|
$this->authorize('view', $dynamic);
|
||||||
|
|
||||||
|
return $dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(20);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing the specified resource.
|
* Show the form for editing the specified resource.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class LedgerController extends Controller
|
|||||||
|
|
||||||
$activityService->updateCursor($request->user(), $ledger);
|
$activityService->updateCursor($request->user(), $ledger);
|
||||||
|
|
||||||
$dynamic->load(['chat', 'participants' => fn($query) => $query->withPivot('display_name')]);
|
$dynamic->load('chat', 'participants');
|
||||||
|
|
||||||
$ledger->load([
|
$ledger->load([
|
||||||
'media',
|
'media',
|
||||||
@ -73,13 +73,7 @@ class LedgerController extends Controller
|
|||||||
},
|
},
|
||||||
'mutations.user',
|
'mutations.user',
|
||||||
'mutations.media',
|
'mutations.media',
|
||||||
'mutations.chat.messages.user',
|
'mutations.chat',
|
||||||
'mutations.chat.messages.media',
|
|
||||||
'mutations.chat.messages.subject' => function ($morphTo) {
|
|
||||||
$morphTo->morphWith([
|
|
||||||
\App\Models\Mutation::class => ['ledger'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$isOwner = $dynamic->participants()
|
$isOwner = $dynamic->participants()
|
||||||
@ -91,9 +85,17 @@ class LedgerController extends Controller
|
|||||||
'dynamic' => $dynamic,
|
'dynamic' => $dynamic,
|
||||||
'ledger' => $ledger,
|
'ledger' => $ledger,
|
||||||
'isOwner' => $isOwner,
|
'isOwner' => $isOwner,
|
||||||
|
'messages' => $dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(20),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function messages(Request $request, Dynamic $dynamic, Ledger $ledger)
|
||||||
|
{
|
||||||
|
$this->authorize('view', $ledger);
|
||||||
|
|
||||||
|
return $dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(20);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing the specified resource.
|
* Show the form for editing the specified resource.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4,11 +4,29 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Models\Dynamic;
|
use App\Models\Dynamic;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
|
|
||||||
class ParticipantController extends Controller
|
class ParticipantController extends Controller
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
|
public function update(Request $request, Dynamic $dynamic)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'display_name' => ['required', 'string', 'max:255'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$participant = $dynamic->participants()->where('user_id', $request->user()->id)->firstOrFail();
|
||||||
|
|
||||||
|
$dynamic->participants()->updateExistingPivot($participant->id, [
|
||||||
|
'display_name' => $request->input('display_name'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Display name updated successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
public function show(Request $request, Dynamic $dynamic, User $user)
|
public function show(Request $request, Dynamic $dynamic, User $user)
|
||||||
{
|
{
|
||||||
// Ensure both the authenticated user and the target user are in the dynamic
|
// Ensure both the authenticated user and the target user are in the dynamic
|
||||||
@ -25,7 +43,7 @@ class ParticipantController extends Controller
|
|||||||
->take(10)
|
->take(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return Inertia::render('Participants/Show', [
|
return Inertia::render('Dynamics/Participants/Show', [
|
||||||
'dynamic' => $dynamic,
|
'dynamic' => $dynamic,
|
||||||
'participant' => [
|
'participant' => [
|
||||||
'id' => $user->id,
|
'id' => $user->id,
|
||||||
@ -37,18 +55,4 @@ class ParticipantController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Request $request, Dynamic $dynamic)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'display_name' => ['required', 'string', 'max:255'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$participant = $dynamic->participants()->where('user_id', $request->user()->id)->firstOrFail();
|
|
||||||
|
|
||||||
$dynamic->participants()->updateExistingPivot($participant->id, [
|
|
||||||
'display_name' => $request->input('display_name'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Display name updated successfully!');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,19 @@
|
|||||||
.c-chat__list {
|
.c-chat__list {
|
||||||
@apply mt-4 flex flex-col gap-3;
|
@apply mt-4 flex flex-col gap-3;
|
||||||
|
|
||||||
|
.c-chat__load-more {
|
||||||
|
@apply mb-4 text-center;
|
||||||
|
|
||||||
|
.c-chat__load-more-btn {
|
||||||
|
@apply text-sm font-semibold;
|
||||||
|
color: var(--primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.c-chat__message {
|
.c-chat__message {
|
||||||
@apply overflow-hidden p-4 shadow-sm sm:rounded-lg;
|
@apply overflow-hidden p-4 shadow-sm sm:rounded-lg;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { useForm, usePage, router } from '@inertiajs/vue3';
|
||||||
import { useForm, usePage } from '@inertiajs/vue3';
|
|
||||||
import { useEcho, echoIsConfigured, configureEcho } from '@laravel/echo-vue';
|
import { useEcho, echoIsConfigured, configureEcho } from '@laravel/echo-vue';
|
||||||
import { route } from 'ziggy-js';
|
|
||||||
import { Paperclip, Info } from '@lucide/vue';
|
import { Paperclip, Info } from '@lucide/vue';
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { route } from 'ziggy-js';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
chat: {
|
chat: {
|
||||||
id: number;
|
id: number;
|
||||||
messages: Array<{
|
messages?: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
user: { id: number; name: string } | null;
|
user: { id: number; name: string } | null;
|
||||||
content: string;
|
content: string;
|
||||||
@ -33,12 +33,36 @@ const props = withDefaults(
|
|||||||
} | null;
|
} | null;
|
||||||
}>;
|
}>;
|
||||||
dynamicId: number;
|
dynamicId: number;
|
||||||
|
initialMessages: {
|
||||||
|
data: Array<any>;
|
||||||
|
next_page_url: string | null;
|
||||||
|
};
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
participants: () => [],
|
participants: () => [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const messages = ref(props.initialMessages.data.reverse());
|
||||||
|
const nextPageUrl = ref(props.initialMessages.next_page_url);
|
||||||
|
|
||||||
|
function loadMoreMessages() {
|
||||||
|
if (!nextPageUrl.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get(nextPageUrl.value, {}, {
|
||||||
|
preserveState: true,
|
||||||
|
preserveScroll: true,
|
||||||
|
only: ['messages'],
|
||||||
|
onSuccess: (page) => {
|
||||||
|
const newMessages = page.props.messages as { data: Array<any>; next_page_url: string | null };
|
||||||
|
messages.value = [...newMessages.data.reverse(), ...messages.value];
|
||||||
|
nextPageUrl.value = newMessages.next_page_url;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!echoIsConfigured()) {
|
if (!echoIsConfigured()) {
|
||||||
configureEcho({
|
configureEcho({
|
||||||
broadcaster: 'reverb',
|
broadcaster: 'reverb',
|
||||||
@ -63,11 +87,24 @@ const form = useForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEcho(`chats.${props.chat.id}`, 'MessageSent', (e: any) => {
|
useEcho(`chats.${props.chat.id}`, 'MessageSent', (e: any) => {
|
||||||
props.chat.messages.push(e.message);
|
messages.value.push(e.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function formatTimestamp(isoString: string): { full: string; time: string } {
|
||||||
|
const date = new Date(isoString);
|
||||||
|
return {
|
||||||
|
full: date.toLocaleString(),
|
||||||
|
time: date.toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const participantsById = computed(() => {
|
const participantsById = computed(() => {
|
||||||
return props.participants.reduce(
|
const list = props.participants || [];
|
||||||
|
return list.reduce(
|
||||||
(acc, p) => {
|
(acc, p) => {
|
||||||
acc[p.id] = p;
|
acc[p.id] = p;
|
||||||
return acc;
|
return acc;
|
||||||
@ -130,7 +167,7 @@ function parseMessageContent(message: {
|
|||||||
/[-\/\\^$*+?.()|[\]{}]/g,
|
/[-\/\\^$*+?.()|[\]{}]/g,
|
||||||
'\\$&',
|
'\\$&',
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameRegex = new RegExp(`"${escapedName}"`, 'g');
|
const nameRegex = new RegExp(`"${escapedName}"`, 'g');
|
||||||
content = content.replace(
|
content = content.replace(
|
||||||
nameRegex,
|
nameRegex,
|
||||||
@ -197,17 +234,20 @@ function closeLightbox() {
|
|||||||
<div class="c-chat">
|
<div class="c-chat">
|
||||||
<h4 class="c-chat__title">Chat</h4>
|
<h4 class="c-chat__title">Chat</h4>
|
||||||
<div class="c-chat__list">
|
<div class="c-chat__list">
|
||||||
|
<div v-if="nextPageUrl" class="c-chat__load-more">
|
||||||
|
<button @click="loadMoreMessages" class="c-chat__load-more-btn">
|
||||||
|
Load More
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="message in chat.messages"
|
v-for="message in messages"
|
||||||
:key="message.id"
|
:key="message.id"
|
||||||
:class="[
|
:class="[
|
||||||
'c-chat__message',
|
'c-chat__message',
|
||||||
{
|
{
|
||||||
'c-chat__message--system': message.user === null,
|
'c-chat__message--system': message.user === null,
|
||||||
'c-chat__message--own': isOwnMessage(message.user?.id),
|
'c-chat__message--own': isOwnMessage(message.user?.id),
|
||||||
'c-chat__message--other': message.user !== null && !isOwnMessage(
|
'c-chat__message--other': message.user !== null && !isOwnMessage(message.user?.id)
|
||||||
message.user?.id,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
@ -217,13 +257,14 @@ function closeLightbox() {
|
|||||||
<span class="c-chat__message-author">{{
|
<span class="c-chat__message-author">{{
|
||||||
message.user.name
|
message.user.name
|
||||||
}}</span>
|
}}</span>
|
||||||
<span class="c-chat__message-time">{{
|
<span
|
||||||
new Date(message.created_at).toLocaleString()
|
class="c-chat__message-time"
|
||||||
}}</span>
|
:title="formatTimestamp(message.created_at).full"
|
||||||
|
>
|
||||||
|
{{ formatTimestamp(message.created_at).time }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="c-chat__message-text">
|
<p class="c-chat__message-text" v-html="parseMessageContent(message)"></p>
|
||||||
{{ message.content }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Attached Media Display -->
|
<!-- Attached Media Display -->
|
||||||
<div
|
<div
|
||||||
@ -265,18 +306,16 @@ function closeLightbox() {
|
|||||||
class="c-chat__system-text"
|
class="c-chat__system-text"
|
||||||
v-html="parseMessageContent(message)"
|
v-html="parseMessageContent(message)"
|
||||||
></span>
|
></span>
|
||||||
<span class="c-chat__system-time">
|
<span
|
||||||
{{
|
class="c-chat__system-time"
|
||||||
new Date(message.created_at).toLocaleTimeString(
|
:title="formatTimestamp(message.created_at).full"
|
||||||
[],
|
>
|
||||||
{ hour: '2-digit', minute: '2-digit' },
|
{{ formatTimestamp(message.created_at).time }}
|
||||||
)
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="chat.messages.length === 0" class="c-chat__empty">
|
<div v-if="messages.length === 0" class="c-chat__empty">
|
||||||
No messages yet.
|
No messages yet.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
31
resources/js/components/ChatMessage.vue
Normal file
31
resources/js/components/ChatMessage.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { route } from 'ziggy-js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
message: {
|
||||||
|
id: number;
|
||||||
|
user: { id: number; name: string } | null;
|
||||||
|
content: string;
|
||||||
|
created_at: string;
|
||||||
|
media?: Array<{
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
file_name: string;
|
||||||
|
mime_type: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const processedContent = computed(() => {
|
||||||
|
return props.message.content.replace(/<user:(\d+)>/g, (match, userId) => {
|
||||||
|
// This is a placeholder for a more robust user lookup
|
||||||
|
return `<a href="${route('users.show', userId)}" class="text-blue-500 hover:underline">@user${userId}</a>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-html="processedContent"></div>
|
||||||
|
</template>
|
||||||
@ -1,5 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { route } from 'ziggy-js';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
dynamicId: number;
|
||||||
participants: Array<{
|
participants: Array<{
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@ -19,7 +23,12 @@ defineProps<{
|
|||||||
:key="participant.id"
|
:key="participant.id"
|
||||||
class="c-participants-list__item"
|
class="c-participants-list__item"
|
||||||
>
|
>
|
||||||
{{ participant.pivot.display_name ?? participant.name }}
|
<Link
|
||||||
|
:href="route('dynamics.users.show', [dynamicId, participant.id])"
|
||||||
|
class="block"
|
||||||
|
>
|
||||||
|
{{ participant.pivot.display_name ?? participant.name }}
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
140
resources/js/pages/Dynamics/Participants/Show.vue
Normal file
140
resources/js/pages/Dynamics/Participants/Show.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Head, Link } from '@inertiajs/vue3';
|
||||||
|
import { route } from 'ziggy-js';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
layout: {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
name: 'Dynamics',
|
||||||
|
href: route('dynamics.index'),
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dynamic.name',
|
||||||
|
href: 'route(\'dynamics.show\', dynamic.id)',
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'participant.display_name',
|
||||||
|
href: 'route(\'dynamics.users.show\', { dynamic: dynamic.id, user: participant.id })',
|
||||||
|
isCurrent: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
dynamic: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
participant: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
display_name: string | null;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
|
mutations: Array<{
|
||||||
|
id: number;
|
||||||
|
amount: number;
|
||||||
|
description: string;
|
||||||
|
status: string;
|
||||||
|
created_at: string;
|
||||||
|
ledger: { id: number; name: string };
|
||||||
|
}>;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const breadcrumbs = [
|
||||||
|
{
|
||||||
|
name: 'Dynamics',
|
||||||
|
href: route('dynamics.index'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: props.dynamic.name,
|
||||||
|
href: route('dynamics.show', props.dynamic.id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: props.participant.display_name ?? props.participant.name,
|
||||||
|
href: route('dynamics.users.show', [props.dynamic.id, props.participant.id]),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Head :title="participant.display_name ?? participant.name" />
|
||||||
|
|
||||||
|
<div class="c-participant-show">
|
||||||
|
<div class="c-participant-show__container">
|
||||||
|
<h2 class="c-participant-show__title">
|
||||||
|
Activity for {{ participant.display_name ?? participant.name }} ({{ participant.role.toUpperCase() }}) in {{ dynamic.name }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="c-participant-show__activity-list">
|
||||||
|
<div
|
||||||
|
v-for="mutation in mutations"
|
||||||
|
:key="mutation.id"
|
||||||
|
class="c-participant-show__activity-item"
|
||||||
|
>
|
||||||
|
<Link :href="route('dynamics.ledgers.show', [dynamic.id, mutation.ledger.id])" class="block">
|
||||||
|
<div class="c-participant-show__activity-meta">
|
||||||
|
<span class="c-participant-show__activity-time">
|
||||||
|
{{ new Date(mutation.created_at).toLocaleString() }}
|
||||||
|
</span>
|
||||||
|
<span class="font-semibold ml-2" :class="mutation.amount > 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'">
|
||||||
|
{{ mutation.amount > 0 ? '+' : '' }}{{ mutation.amount }}
|
||||||
|
</span>
|
||||||
|
<span class="text-neutral-400 ml-2">on {{ mutation.ledger.name }}</span>
|
||||||
|
<span class="uppercase text-xs px-1.5 py-0.5 rounded ml-auto" :class="mutation.status === 'approved' ? 'bg-green-100 text-green-700 dark:bg-green-950/30 dark:text-green-400' : 'bg-yellow-100 text-yellow-700 dark:bg-yellow-950/30 dark:text-yellow-400'">
|
||||||
|
{{ mutation.status }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="c-participant-show__activity-desc">
|
||||||
|
{{ mutation.description }}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div v-if="mutations.length === 0" class="text-neutral-500 text-sm">
|
||||||
|
No mutations recorded for this participant in this Dynamic yet.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@reference "../../../../css/app.css";
|
||||||
|
|
||||||
|
.c-participant-show {
|
||||||
|
@apply py-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__container {
|
||||||
|
@apply mx-auto max-w-7xl sm:px-6 lg:px-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__title {
|
||||||
|
@apply mb-6 text-xl font-bold tracking-tight text-neutral-900 dark:text-neutral-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__activity-list {
|
||||||
|
@apply space-y-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__activity-item {
|
||||||
|
@apply rounded-lg border p-4 transition-colors hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-900;
|
||||||
|
background-color: var(--card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__activity-meta {
|
||||||
|
@apply mb-1.5 flex items-center text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__activity-time {
|
||||||
|
@apply text-neutral-400 dark:text-neutral-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c-participant-show__activity-desc {
|
||||||
|
@apply text-sm text-neutral-600 dark:text-neutral-400 mt-1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -5,6 +5,23 @@ import LedgerList from '@/components/LedgerList.vue';
|
|||||||
import { Head, Link as InertiaLink } from '@inertiajs/vue3';
|
import { Head, Link as InertiaLink } from '@inertiajs/vue3';
|
||||||
import { route } from 'ziggy-js';
|
import { route } from 'ziggy-js';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
layout: {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
name: 'Dynamics',
|
||||||
|
href: route('dynamics.index'),
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dynamic.name',
|
||||||
|
href: 'route(\'dynamics.show\', dynamic.id)',
|
||||||
|
isCurrent: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
dynamic: {
|
dynamic: {
|
||||||
id: number;
|
id: number;
|
||||||
@ -21,6 +38,10 @@ const props = defineProps<{
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
|
messages: {
|
||||||
|
data: Array<any>;
|
||||||
|
next_page_url: string | null;
|
||||||
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
||||||
@ -58,10 +79,10 @@ const breadcrumbs = [
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dynamic Chat -->
|
<!-- Dynamic Chat -->
|
||||||
<Chat :chat="dynamic.chat" :participants="dynamic.participants" :dynamic-id="dynamic.id" />
|
<Chat :chat="dynamic.chat" :initial-messages="messages" :participants="dynamic.participants" :dynamic-id="dynamic.id" />
|
||||||
|
|
||||||
<!-- Participants Component -->
|
<!-- Participants Component -->
|
||||||
<ParticipantsList :participants="dynamic.participants" />
|
<ParticipantsList :dynamic-id="dynamic.id" :participants="dynamic.participants" />
|
||||||
|
|
||||||
<!-- Ledgers List Component -->
|
<!-- Ledgers List Component -->
|
||||||
<LedgerList :dynamic-id="dynamic.id" :ledgers="dynamic.ledgers" />
|
<LedgerList :dynamic-id="dynamic.id" :ledgers="dynamic.ledgers" />
|
||||||
|
|||||||
@ -4,8 +4,31 @@ import { useEcho } from '@laravel/echo-vue';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { route } from 'ziggy-js';
|
import { route } from 'ziggy-js';
|
||||||
import AddMutationForm from '@/components/AddMutationForm.vue';
|
import AddMutationForm from '@/components/AddMutationForm.vue';
|
||||||
|
import Chat from '@/components/Chat.vue';
|
||||||
import MutationList from '@/components/MutationList.vue';
|
import MutationList from '@/components/MutationList.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
layout: {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
name: 'Dynamics',
|
||||||
|
href: route('dynamics.index'),
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dynamic.name',
|
||||||
|
href: 'route(\'dynamics.show\', dynamic.id)',
|
||||||
|
isCurrent: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ledger.name',
|
||||||
|
href: 'route(\'dynamics.ledgers.show\', { dynamic: dynamic.id, ledger: ledger.id })',
|
||||||
|
isCurrent: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
dynamic: {
|
dynamic: {
|
||||||
id: number;
|
id: number;
|
||||||
@ -37,6 +60,10 @@ const props = defineProps<{
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
|
messages: {
|
||||||
|
data: Array<any>;
|
||||||
|
next_page_url: string | null;
|
||||||
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
@ -258,6 +285,8 @@ function isOwnerUser(userId: number): boolean {
|
|||||||
:ledger-alignment="ledger.alignment"
|
:ledger-alignment="ledger.alignment"
|
||||||
@open-lightbox="openLightbox"
|
@open-lightbox="openLightbox"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Chat :chat="dynamic.chat" :initial-messages="messages" :participants="dynamic.participants" :dynamic-id="dynamic.id" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,10 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::resource('dynamics', DynamicController::class)->except(['edit', 'update']);
|
Route::resource('dynamics', DynamicController::class)->except(['edit', 'update']);
|
||||||
Route::get('/dynamics/{dynamic}/settings', [DynamicController::class, 'edit'])->name('dynamics.edit');
|
Route::get('/dynamics/{dynamic}/settings', [DynamicController::class, 'edit'])->name('dynamics.edit');
|
||||||
Route::patch('/dynamics/{dynamic}/settings', [DynamicController::class, 'update'])->name('dynamics.update');
|
Route::patch('/dynamics/{dynamic}/settings', [DynamicController::class, 'update'])->name('dynamics.update');
|
||||||
|
Route::get('/dynamics/{dynamic}/messages', [DynamicController::class, 'messages'])->name('dynamics.messages');
|
||||||
|
|
||||||
Route::get('/dynamics/{dynamic}/ledgers/create', [LedgerController::class, 'create'])->name('dynamics.ledgers.create');
|
Route::get('/dynamics/{dynamic}/ledgers/create', [LedgerController::class, 'create'])->name('dynamics.ledgers.create');
|
||||||
|
Route::get('/dynamics/{dynamic}/ledgers/{ledger}/messages', [LedgerController::class, 'messages'])->name('dynamics.ledgers.messages');
|
||||||
Route::resource('dynamics.ledgers', LedgerController::class)->scoped()->except(['create']);
|
Route::resource('dynamics.ledgers', LedgerController::class)->scoped()->except(['create']);
|
||||||
|
|
||||||
Route::resource('dynamics.ledgers.predefined-mutations', \App\Http\Controllers\PredefinedMutationController::class)->scoped();
|
Route::resource('dynamics.ledgers.predefined-mutations', \App\Http\Controllers\PredefinedMutationController::class)->scoped();
|
||||||
|
|||||||
@ -19,7 +19,7 @@ test('authenticated participant can view another participant detail page in dyna
|
|||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertInertia(fn ($page) => $page
|
$response->assertInertia(fn ($page) => $page
|
||||||
->component('Participants/Show')
|
->component('Dynamics/Participants/Show')
|
||||||
->has('dynamic')
|
->has('dynamic')
|
||||||
->where('participant.id', $participant->id)
|
->where('participant.id', $participant->id)
|
||||||
->where('participant.name', $participant->name)
|
->where('participant.name', $participant->name)
|
||||||
@ -86,7 +86,7 @@ test('participant detail page displays their recent mutations in dynamic', funct
|
|||||||
|
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
$response->assertInertia(fn ($page) => $page
|
$response->assertInertia(fn ($page) => $page
|
||||||
->component('Participants/Show')
|
->component('Dynamics/Participants/Show')
|
||||||
->where('participant.display_name', 'Bitch')
|
->where('participant.display_name', 'Bitch')
|
||||||
->has('mutations', 2)
|
->has('mutations', 2)
|
||||||
->where('mutations.0.description', 'Infraction 1') // Ordered latest first
|
->where('mutations.0.description', 'Infraction 1') // Ordered latest first
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user