feat: add jira filter url per participant and meeting jira url
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
let editingId = null
|
||||
let editName = ''
|
||||
let editTimeLimitMin = 2
|
||||
let editJiraFilter = ''
|
||||
|
||||
// Meeting name editing
|
||||
let editingMeetingName = false
|
||||
@@ -26,6 +27,9 @@
|
||||
let editingMeetingTime = false
|
||||
let meetingTimeInput = 60
|
||||
|
||||
// Meeting Jira URL editing
|
||||
let editingMeetingJiraUrl = false
|
||||
let meetingJiraUrlInput = ''
|
||||
onMount(async () => {
|
||||
await loadData()
|
||||
})
|
||||
@@ -48,7 +52,7 @@
|
||||
if (!newName.trim()) return
|
||||
|
||||
try {
|
||||
await AddParticipant(newName.trim(), '', newTimeLimitMin * 60)
|
||||
await AddParticipant(newName.trim(), '', newTimeLimitMin * 60, '')
|
||||
newName = ''
|
||||
await loadData()
|
||||
} catch (e) {
|
||||
@@ -71,19 +75,21 @@
|
||||
editingId = p.id
|
||||
editName = p.name
|
||||
editTimeLimitMin = Math.floor(p.timeLimit / 60)
|
||||
editJiraFilter = p.jiraFilter || ''
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editingId = null
|
||||
editName = ''
|
||||
editTimeLimitMin = 2
|
||||
editJiraFilter = ''
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
if (!editName.trim() || editingId === null) return
|
||||
|
||||
try {
|
||||
await UpdateParticipant(editingId, editName.trim(), '', editTimeLimitMin * 60)
|
||||
await UpdateParticipant(editingId, editName.trim(), '', editTimeLimitMin * 60, editJiraFilter.trim())
|
||||
editingId = null
|
||||
await loadData()
|
||||
} catch (e) {
|
||||
@@ -187,7 +193,7 @@
|
||||
async function saveMeetingName() {
|
||||
if (!meetingNameInput.trim()) return
|
||||
try {
|
||||
await UpdateMeeting(meetingNameInput.trim(), meeting?.timeLimit || 3600)
|
||||
await UpdateMeeting(meetingNameInput.trim(), meeting?.timeLimit || 3600, meeting?.jiraUrl || '')
|
||||
meeting = await GetMeeting()
|
||||
editingMeetingName = false
|
||||
} catch (e) {
|
||||
@@ -207,7 +213,7 @@
|
||||
async function saveMeetingTime() {
|
||||
if (meetingTimeInput < 1) return
|
||||
try {
|
||||
await UpdateMeeting(meeting?.name || 'Daily Standup', meetingTimeInput * 60)
|
||||
await UpdateMeeting(meeting?.name || 'Daily Standup', meetingTimeInput * 60, meeting?.jiraUrl || '')
|
||||
meeting = await GetMeeting()
|
||||
editingMeetingTime = false
|
||||
} catch (e) {
|
||||
@@ -215,11 +221,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
function startEditMeetingJiraUrl() {
|
||||
meetingJiraUrlInput = meeting?.jiraUrl || ''
|
||||
editingMeetingJiraUrl = true
|
||||
}
|
||||
|
||||
function cancelEditMeetingJiraUrl() {
|
||||
editingMeetingJiraUrl = false
|
||||
meetingJiraUrlInput = ''
|
||||
}
|
||||
|
||||
async function saveMeetingJiraUrl() {
|
||||
try {
|
||||
await UpdateMeeting(meeting?.name || 'Daily Standup', meeting?.timeLimit || 3600, meetingJiraUrlInput.trim())
|
||||
meeting = await GetMeeting()
|
||||
editingMeetingJiraUrl = false
|
||||
} catch (e) {
|
||||
console.error('Failed to update meeting jira url:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function handleGlobalKeydown(e) {
|
||||
if (e.key === 'Escape') {
|
||||
if (editingId !== null) cancelEdit()
|
||||
if (editingMeetingName) cancelEditMeetingName()
|
||||
if (editingMeetingTime) cancelEditMeetingTime()
|
||||
if (editingMeetingJiraUrl) cancelEditMeetingJiraUrl()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -275,6 +302,29 @@
|
||||
<span class="edit-icon">✎</span>
|
||||
</p>
|
||||
{/if}
|
||||
{#if editingMeetingJiraUrl}
|
||||
<div class="meeting-jira-edit">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
type="url"
|
||||
bind:value={meetingJiraUrlInput}
|
||||
placeholder={$t('setup.jiraUrlPlaceholder')}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter') saveMeetingJiraUrl()
|
||||
if (e.key === 'Escape') cancelEditMeetingJiraUrl()
|
||||
}}
|
||||
autofocus
|
||||
/>
|
||||
<button class="save-btn" on:click={saveMeetingJiraUrl}>✓</button>
|
||||
<button class="cancel-btn" on:click={cancelEditMeetingJiraUrl}>✗</button>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
|
||||
<p on:click={startEditMeetingJiraUrl} class="editable-jira">
|
||||
{$t('setup.jiraUrl')}: {meeting?.jiraUrl ? '🔗' : '—'}
|
||||
<span class="edit-icon">✎</span>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="add-participant">
|
||||
@@ -334,6 +384,9 @@
|
||||
|
||||
<span class="name">{p.name}</span>
|
||||
<span class="time-limit">{Math.floor(p.timeLimit / 60)} {$t('setup.minutes')}</span>
|
||||
{#if p.jiraFilter}
|
||||
<span class="url-indicator" title="{meeting?.jiraUrl}&quickFilter={p.jiraFilter}">🔗</span>
|
||||
{/if}
|
||||
|
||||
<button class="edit" on:click={() => startEdit(p)} title="{$t('participants.edit')}">✎</button>
|
||||
<button class="remove" on:click={() => handleRemove(id)}>×</button>
|
||||
@@ -363,6 +416,13 @@
|
||||
if (e.key === 'Escape') cancelEdit()
|
||||
}} />
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label for="editJiraFilter">{$t('participants.jiraFilter')}</label>
|
||||
<input id="editJiraFilter" type="text" bind:value={editJiraFilter} placeholder={$t('participants.jiraFilterPlaceholder')} on:keydown={(e) => {
|
||||
if (e.key === 'Enter') saveEdit()
|
||||
if (e.key === 'Escape') cancelEdit()
|
||||
}} />
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="cancel-btn" on:click={cancelEdit}>{$t('common.cancel')}</button>
|
||||
<button class="save-btn" on:click={saveEdit}>{$t('common.save')}</button>
|
||||
@@ -529,6 +589,62 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header p.editable-jira {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
margin: 4px 0 0 0;
|
||||
}
|
||||
|
||||
.header p.editable-jira:hover {
|
||||
color: #4a90d9;
|
||||
}
|
||||
|
||||
.header p.editable-jira:hover .edit-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.meeting-jira-edit {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.meeting-jira-edit input {
|
||||
padding: 5px 8px;
|
||||
border: 1px solid #4a90d9;
|
||||
border-radius: 8px;
|
||||
background: #1b2636;
|
||||
color: #e0e0e0;
|
||||
font-size: 12px;
|
||||
width: 280px;
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.meeting-jira-edit .save-btn {
|
||||
padding: 5px 10px;
|
||||
background: #166534;
|
||||
color: #4ade80;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.meeting-jira-edit .cancel-btn {
|
||||
padding: 5px 10px;
|
||||
background: #991b1b;
|
||||
color: #fca5a5;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-participant {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
@@ -677,6 +793,13 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.url-indicator {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
cursor: default;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.edit {
|
||||
padding: 4px 8px;
|
||||
background: transparent;
|
||||
|
||||
@@ -27,6 +27,8 @@ export const translations = {
|
||||
minutes: 'мин',
|
||||
unlimited: 'Без ограничения',
|
||||
dragHint: 'перетащите для изменения порядка, ✓/✗ присутствие',
|
||||
jiraUrl: 'Jira URL собрания',
|
||||
jiraUrlPlaceholder: 'https://jira.ncloudtech.ru/...',
|
||||
},
|
||||
|
||||
// Timer page
|
||||
@@ -139,6 +141,8 @@ export const translations = {
|
||||
edit: 'Редактировать',
|
||||
delete: 'Удалить',
|
||||
name: 'Имя',
|
||||
jiraFilter: 'Jira Quick Filter',
|
||||
jiraFilterPlaceholder: 'quickFilter ID (напр. 12345)',
|
||||
stats: 'Статистика',
|
||||
avgSpeakTime: 'Среднее время выступления',
|
||||
totalMeetings: 'Всего собраний',
|
||||
@@ -200,6 +204,8 @@ export const translations = {
|
||||
minutes: 'min',
|
||||
unlimited: 'Unlimited',
|
||||
dragHint: 'drag to reorder, ✓/✗ attendance',
|
||||
jiraUrl: 'Meeting Jira URL',
|
||||
jiraUrlPlaceholder: 'https://jira.ncloudtech.ru/...',
|
||||
},
|
||||
|
||||
// Timer page
|
||||
@@ -312,6 +318,8 @@ export const translations = {
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
name: 'Name',
|
||||
jiraFilter: 'Jira Quick Filter',
|
||||
jiraFilterPlaceholder: 'quickFilter ID (e.g. 12345)',
|
||||
stats: 'Statistics',
|
||||
avgSpeakTime: 'Avg Speaking Time',
|
||||
totalMeetings: 'Total Meetings',
|
||||
|
||||
6
frontend/wailsjs/go/app/App.d.ts
vendored
6
frontend/wailsjs/go/app/App.d.ts
vendored
@@ -3,7 +3,7 @@
|
||||
import {models} from '../models';
|
||||
import {updater} from '../models';
|
||||
|
||||
export function AddParticipant(arg1:string,arg2:string,arg3:number):Promise<models.Participant>;
|
||||
export function AddParticipant(arg1:string,arg2:string,arg3:number,arg4:string):Promise<models.Participant>;
|
||||
|
||||
export function CheckForUpdates():Promise<updater.UpdateInfo>;
|
||||
|
||||
@@ -65,8 +65,8 @@ export function StopMeeting():Promise<void>;
|
||||
|
||||
export function SwitchToSpeaker(arg1:number):Promise<void>;
|
||||
|
||||
export function UpdateMeeting(arg1:string,arg2:number):Promise<void>;
|
||||
export function UpdateMeeting(arg1:string,arg2:number,arg3:string):Promise<void>;
|
||||
|
||||
export function UpdateParticipant(arg1:number,arg2:string,arg3:string,arg4:number):Promise<void>;
|
||||
export function UpdateParticipant(arg1:number,arg2:string,arg3:string,arg4:number,arg5:string):Promise<void>;
|
||||
|
||||
export function UpdateSettings(arg1:models.Settings):Promise<void>;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function AddParticipant(arg1, arg2, arg3) {
|
||||
return window['go']['app']['App']['AddParticipant'](arg1, arg2, arg3);
|
||||
export function AddParticipant(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['app']['App']['AddParticipant'](arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
export function CheckForUpdates() {
|
||||
@@ -126,12 +126,12 @@ export function SwitchToSpeaker(arg1) {
|
||||
return window['go']['app']['App']['SwitchToSpeaker'](arg1);
|
||||
}
|
||||
|
||||
export function UpdateMeeting(arg1, arg2) {
|
||||
return window['go']['app']['App']['UpdateMeeting'](arg1, arg2);
|
||||
export function UpdateMeeting(arg1, arg2, arg3) {
|
||||
return window['go']['app']['App']['UpdateMeeting'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function UpdateParticipant(arg1, arg2, arg3, arg4) {
|
||||
return window['go']['app']['App']['UpdateParticipant'](arg1, arg2, arg3, arg4);
|
||||
export function UpdateParticipant(arg1, arg2, arg3, arg4, arg5) {
|
||||
return window['go']['app']['App']['UpdateParticipant'](arg1, arg2, arg3, arg4, arg5);
|
||||
}
|
||||
|
||||
export function UpdateSettings(arg1) {
|
||||
|
||||
@@ -112,6 +112,7 @@ export namespace models {
|
||||
id: number;
|
||||
name: string;
|
||||
email?: string;
|
||||
jiraFilter?: string;
|
||||
timeLimit: number;
|
||||
order: number;
|
||||
active: boolean;
|
||||
@@ -129,6 +130,7 @@ export namespace models {
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.email = source["email"];
|
||||
this.jiraFilter = source["jiraFilter"];
|
||||
this.timeLimit = source["timeLimit"];
|
||||
this.order = source["order"];
|
||||
this.active = source["active"];
|
||||
@@ -253,6 +255,7 @@ export namespace models {
|
||||
export class Meeting {
|
||||
id: number;
|
||||
name: string;
|
||||
jiraUrl?: string;
|
||||
timeLimit: number;
|
||||
sessions?: MeetingSession[];
|
||||
// Go type: time
|
||||
@@ -268,6 +271,7 @@ export namespace models {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.name = source["name"];
|
||||
this.jiraUrl = source["jiraUrl"];
|
||||
this.timeLimit = source["timeLimit"];
|
||||
this.sessions = this.convertValues(source["sessions"], MeetingSession);
|
||||
this.createdAt = this.convertValues(source["createdAt"], null);
|
||||
|
||||
Reference in New Issue
Block a user