diff --git a/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue b/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue
index dfe700a8..b1c20a77 100644
--- a/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue
+++ b/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue
@@ -5,104 +5,63 @@
:title="t('dialog.previous_instances.header')"
width="1000px"
append-to-body>
-
-
-
-
-
-
-
-
- {{ formatDateFilter(scope.row.created_at, 'long') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
diff --git a/src/components/dialogs/PreviousInstancesDialog/previousInstancesGroupColumns.jsx b/src/components/dialogs/PreviousInstancesDialog/previousInstancesGroupColumns.jsx
new file mode 100644
index 00000000..7317098a
--- /dev/null
+++ b/src/components/dialogs/PreviousInstancesDialog/previousInstancesGroupColumns.jsx
@@ -0,0 +1,121 @@
+import { ArrowUpDown } from 'lucide-vue-next';
+
+import Location from '../../Location.vue';
+import { Button } from '../../ui/button';
+import { i18n } from '../../../plugin';
+import { formatDateFilter } from '../../../shared/utils';
+
+const { t } = i18n.global;
+
+const sortButton = ({ column, label, descFirst = false }) => (
+
+);
+
+const resolveBool = (maybeRef) => {
+ if (maybeRef && typeof maybeRef === 'object' && 'value' in maybeRef) {
+ return !!maybeRef.value;
+ }
+ return !!maybeRef;
+};
+
+export const createColumns = ({ shiftHeld, onShowInfo, onDelete, onDeletePrompt }) => [
+ {
+ id: 'created_at',
+ accessorFn: (row) => (row?.created_at ? Date.parse(row.created_at) : 0),
+ size: 170,
+ header: ({ column }) =>
+ sortButton({
+ column,
+ label: t('table.previous_instances.date'),
+ descFirst: true
+ }),
+ cell: ({ row }) => (
+ {formatDateFilter(row.original?.created_at, 'long')}
+ )
+ },
+ {
+ id: 'instance',
+ accessorFn: (row) => row?.worldName ?? row?.name ?? '',
+ header: () => t('table.previous_instances.instance_name'),
+ meta: {
+ stretch: true
+ },
+ cell: ({ row }) => (
+
+ )
+ },
+ {
+ id: 'time',
+ accessorFn: (row) => row?.time ?? 0,
+ size: 100,
+ header: ({ column }) =>
+ sortButton({ column, label: t('table.previous_instances.time') }),
+ cell: ({ row }) => {row.original?.timer ?? ''}
+ },
+ {
+ id: 'actions',
+ enableSorting: false,
+ size: 120,
+ header: () => t('table.previous_instances.action'),
+ meta: {
+ thClass: 'text-right',
+ tdClass: 'text-right'
+ },
+ cell: ({ row }) => {
+ const original = row.original;
+ const isShiftHeld = resolveBool(shiftHeld);
+
+ return (
+
+
+
+
+
+ );
+ }
+ }
+];
diff --git a/src/components/dialogs/PreviousInstancesDialog/previousInstancesInfoColumns.jsx b/src/components/dialogs/PreviousInstancesDialog/previousInstancesInfoColumns.jsx
new file mode 100644
index 00000000..043d296c
--- /dev/null
+++ b/src/components/dialogs/PreviousInstancesDialog/previousInstancesInfoColumns.jsx
@@ -0,0 +1,104 @@
+import { ArrowUpDown } from 'lucide-vue-next';
+
+import { Button } from '../../ui/button';
+import { i18n } from '../../../plugin';
+import { formatDateFilter } from '../../../shared/utils';
+
+const { t } = i18n.global;
+
+const sortButton = ({ column, label, descFirst = false }) => (
+
+);
+
+export const createColumns = ({ onLookupUser }) => [
+ {
+ id: 'created_at',
+ accessorFn: (row) => (row?.created_at ? Date.parse(row.created_at) : 0),
+ size: 170,
+ header: ({ column }) =>
+ sortButton({
+ column,
+ label: t('table.previous_instances.date'),
+ descFirst: true
+ }),
+ cell: ({ row }) => {
+ const createdAt = row.original?.created_at;
+ const shortText = formatDateFilter(createdAt, 'short');
+ const longText = formatDateFilter(createdAt, 'long');
+ return {shortText};
+ }
+ },
+ {
+ id: 'friend',
+ accessorFn: (row) => (row?.isFavorite ? 2 : row?.isFriend ? 1 : 0),
+ size: 70,
+ enableSorting: false,
+ header: () => t('table.gameLog.icon'),
+ meta: {
+ thClass: 'text-center',
+ tdClass: 'text-center'
+ },
+ cell: ({ row }) => {
+ const original = row.original;
+ if (original?.isFavorite) {
+ return ⭐;
+ }
+ if (original?.isFriend) {
+ return 💚;
+ }
+ return null;
+ }
+ },
+ {
+ id: 'displayName',
+ accessorFn: (row) => row?.displayName ?? '',
+ header: ({ column }) =>
+ sortButton({ column, label: t('table.previous_instances.display_name') }),
+ meta: {
+ stretch: true
+ },
+ cell: ({ row }) => {
+ const original = row.original;
+ return (
+ {
+ event.stopPropagation();
+ onLookupUser?.(original);
+ }}
+ >
+ {original?.displayName ?? ''}
+
+ );
+ }
+ },
+ {
+ id: 'time',
+ accessorFn: (row) => row?.time ?? 0,
+ size: 100,
+ header: ({ column }) => sortButton({ column, label: t('table.previous_instances.time') }),
+ cell: ({ row }) => {row.original?.timer ?? ''}
+ },
+ {
+ id: 'count',
+ accessorFn: (row) => row?.count ?? 0,
+ size: 100,
+ header: ({ column }) => sortButton({ column, label: t('table.previous_instances.count') }),
+ cell: ({ row }) => {row.original?.count ?? ''}
+ }
+];
diff --git a/src/components/dialogs/PreviousInstancesDialog/previousInstancesWorldColumns.jsx b/src/components/dialogs/PreviousInstancesDialog/previousInstancesWorldColumns.jsx
new file mode 100644
index 00000000..ecd2e464
--- /dev/null
+++ b/src/components/dialogs/PreviousInstancesDialog/previousInstancesWorldColumns.jsx
@@ -0,0 +1,142 @@
+import { ArrowUpDown } from 'lucide-vue-next';
+
+import DisplayName from '../../DisplayName.vue';
+import LocationWorld from '../../LocationWorld.vue';
+import { Button } from '../../ui/button';
+import { i18n } from '../../../plugin';
+import { formatDateFilter } from '../../../shared/utils';
+
+const { t } = i18n.global;
+
+const sortButton = ({ column, label, descFirst = false }) => (
+
+);
+
+const resolveBool = (maybeRef) => {
+ if (maybeRef && typeof maybeRef === 'object' && 'value' in maybeRef) {
+ return !!maybeRef.value;
+ }
+ return !!maybeRef;
+};
+
+export const createColumns = ({
+ shiftHeld,
+ currentUserId,
+ forceUpdateKey,
+ onShowInfo,
+ onDelete,
+ onDeletePrompt
+}) => [
+ {
+ id: 'created_at',
+ accessorFn: (row) => (row?.created_at ? Date.parse(row.created_at) : 0),
+ size: 170,
+ header: ({ column }) =>
+ sortButton({
+ column,
+ label: t('table.previous_instances.date'),
+ descFirst: true
+ }),
+ cell: ({ row }) => (
+ {formatDateFilter(row.original?.created_at, 'long')}
+ )
+ },
+ {
+ id: 'instance',
+ accessorFn: (row) => row?.$location?.tag ?? row?.location ?? '',
+ header: () => t('table.previous_instances.instance_name'),
+ meta: {
+ stretch: true
+ },
+ cell: ({ row }) => (
+
+ )
+ },
+ {
+ id: 'creator',
+ accessorFn: (row) => row?.$location?.userId ?? '',
+ size: 170,
+ header: () => t('table.previous_instances.instance_creator'),
+ cell: ({ row }) => (
+
+ )
+ },
+ {
+ id: 'time',
+ accessorFn: (row) => row?.time ?? 0,
+ size: 100,
+ header: ({ column }) =>
+ sortButton({ column, label: t('table.previous_instances.time') }),
+ cell: ({ row }) => {row.original?.timer ?? ''}
+ },
+ {
+ id: 'actions',
+ enableSorting: false,
+ size: 120,
+ header: () => t('table.previous_instances.action'),
+ meta: {
+ thClass: 'text-right',
+ tdClass: 'text-right'
+ },
+ cell: ({ row }) => {
+ const original = row.original;
+ const isShiftHeld = resolveBool(shiftHeld);
+
+ return (
+
+
+
+
+
+ );
+ }
+ }
+];
diff --git a/src/components/dialogs/UserDialog/PreviousInstancesUserDialog.vue b/src/components/dialogs/UserDialog/PreviousInstancesUserDialog.vue
index fb3b060e..01d16eb7 100644
--- a/src/components/dialogs/UserDialog/PreviousInstancesUserDialog.vue
+++ b/src/components/dialogs/UserDialog/PreviousInstancesUserDialog.vue
@@ -5,79 +5,31 @@
:title="t('dialog.previous_instances.header')"
width="1000px"
append-to-body>
-
-
-
-
-
-
-
- {{ formatDateFilter(scope.row.created_at, 'long') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
diff --git a/src/components/ui/input-group/InputGroupTextareaField.vue b/src/components/ui/input-group/InputGroupTextareaField.vue
index 5797fed6..d10aad38 100644
--- a/src/components/ui/input-group/InputGroupTextareaField.vue
+++ b/src/components/ui/input-group/InputGroupTextareaField.vue
@@ -1,8 +1,8 @@
-
+