@unson/brainbase-mcp 0.41.10 → 0.41.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/judgment-history.js +76 -3
- package/dist/local-web-host.js +19 -2
- package/package.json +6 -2
- package/ui/area-nav.css +79 -0
- package/ui/area-nav.js +249 -0
- package/ui/foundation-overview.css +9 -0
- package/ui/foundation-overview.js +75 -5
- package/ui/graph-ontology-view.css +88 -0
- package/ui/graph-ontology-view.js +248 -0
- package/ui/graph-own-share.js +167 -0
- package/ui/graph-registry-view.css +272 -50
- package/ui/graph-registry-view.js +539 -456
- package/ui/graph-view-shared.css +32 -0
- package/ui/graph-view-shared.js +191 -12
- package/ui/judgment-history.css +155 -2
- package/ui/judgment-history.js +649 -241
- package/ui/local-web-shell.css +1 -13
- package/ui/local-web-shell.js +80 -40
- package/ui/meeting-minutes.css +87 -1
- package/ui/meeting-minutes.js +275 -30
- package/ui/outcome-knowledge.js +6 -2
- package/ui/people-and-companies-view.css +166 -15
- package/ui/people-and-companies-view.js +307 -93
- package/ui/world/world-canvas-notes.js +3 -2
- package/ui/world/world-view.css +19 -0
- package/ui/world/world-view.js +52 -10
- package/ui/world/world-work-rail.js +114 -8
package/ui/meeting-minutes.js
CHANGED
|
@@ -7,12 +7,27 @@
|
|
|
7
7
|
*
|
|
8
8
|
* List order and labels are a projection. Canonical records are not rewritten.
|
|
9
9
|
* Calendar links stay out until a meeting-side event id is verified.
|
|
10
|
+
* Filters and sort only narrow the loaded list; the host owns any filter that
|
|
11
|
+
* changes what it asks the server for (story-meeting-minutes-list-filters-v1).
|
|
10
12
|
*/
|
|
11
13
|
|
|
14
|
+
import { workspaceCombobox } from './workspace-kit.js';
|
|
15
|
+
|
|
12
16
|
export const MEETING_MINUTES_UI_CONTRACT_VERSION = 'brainbase.meeting-minutes-ui.v1';
|
|
13
17
|
|
|
14
18
|
const WEEKDAYS = ['日', '月', '火', '水', '木', '金', '土'];
|
|
15
19
|
const TOKYO = 'Asia/Tokyo';
|
|
20
|
+
const SORTS = Object.freeze([
|
|
21
|
+
{ value: 'scheduled-desc', label: '会議の日時が新しい順' },
|
|
22
|
+
{ value: 'scheduled-asc', label: '会議の日時が古い順' },
|
|
23
|
+
{ value: 'updated-desc', label: '議事録の更新が新しい順' },
|
|
24
|
+
]);
|
|
25
|
+
const STATE_FILTERS = Object.freeze([
|
|
26
|
+
{ value: '', label: 'すべての状態' },
|
|
27
|
+
{ value: 'pending', label: '確認待ち' },
|
|
28
|
+
{ value: 'confirmed', label: '確認済み' },
|
|
29
|
+
]);
|
|
30
|
+
const EMPTY_FILTERS = Object.freeze({ query: '', from: '', to: '', participant: '', status: '' });
|
|
16
31
|
|
|
17
32
|
function tokyoParts(date) {
|
|
18
33
|
const parts = new Intl.DateTimeFormat('en-US', {
|
|
@@ -279,19 +294,71 @@ function byCreatedAt(versions) {
|
|
|
279
294
|
.map((item) => item.version);
|
|
280
295
|
}
|
|
281
296
|
|
|
282
|
-
function
|
|
297
|
+
function updatedTime(entry) {
|
|
298
|
+
let latest = latestVersionTime(entry);
|
|
299
|
+
for (const documentRecord of Array.isArray(entry?.minutes) ? entry.minutes : []) {
|
|
300
|
+
const date = parseDate(documentRecord?.updated_at);
|
|
301
|
+
if (date && (!latest || date > latest)) latest = date;
|
|
302
|
+
}
|
|
303
|
+
return latest ?? importTime(entry);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function sortMeetings(meetings, sort = 'scheduled-desc') {
|
|
307
|
+
const byUpdate = sort === 'updated-desc';
|
|
308
|
+
const direction = sort === 'scheduled-asc' ? 1 : -1;
|
|
283
309
|
return meetings
|
|
284
|
-
.map((entry, index) =>
|
|
310
|
+
.map((entry, index) => {
|
|
311
|
+
const timing = meetingTiming(entry);
|
|
312
|
+
const updated = byUpdate ? updatedTime(entry) : null;
|
|
313
|
+
return {
|
|
314
|
+
entry,
|
|
315
|
+
index,
|
|
316
|
+
bottom: byUpdate ? !updated : timing.kind === 'unregistered',
|
|
317
|
+
at: byUpdate ? updated?.getTime() ?? Number.NEGATIVE_INFINITY : timing.at,
|
|
318
|
+
};
|
|
319
|
+
})
|
|
285
320
|
.sort((left, right) => {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (
|
|
289
|
-
if (left.timing.at !== right.timing.at) return right.timing.at - left.timing.at;
|
|
321
|
+
if (left.bottom !== right.bottom) return left.bottom ? 1 : -1;
|
|
322
|
+
// Meetings without a date keep the newest import first in every order.
|
|
323
|
+
if (left.at !== right.at) return left.bottom ? right.at - left.at : (left.at - right.at) * direction;
|
|
290
324
|
return left.index - right.index;
|
|
291
325
|
})
|
|
292
326
|
.map((item) => item.entry);
|
|
293
327
|
}
|
|
294
328
|
|
|
329
|
+
function searchText(value) {
|
|
330
|
+
return String(value ?? '').normalize('NFKC').toLocaleLowerCase('ja');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function tokyoDateValue(date) {
|
|
334
|
+
const { year, month, day } = tokyoParts(date);
|
|
335
|
+
return `${year}-${pad(month)}-${pad(day)}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function participantsOf(entry) {
|
|
339
|
+
const ids = entry?.meeting?.participant_ids;
|
|
340
|
+
return Array.isArray(ids) ? ids.map((id) => text(id)).filter(Boolean) : [];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function matchesFilters(entry, filters) {
|
|
344
|
+
const terms = searchText(filters.query).split(/\s+/u).filter(Boolean);
|
|
345
|
+
if (terms.length) {
|
|
346
|
+
const documents = Array.isArray(entry?.minutes) ? entry.minutes : [];
|
|
347
|
+
const haystack = searchText([entry?.meeting?.title, ...documents.map((documentRecord) => documentRecord?.title)].join('\n'));
|
|
348
|
+
if (!terms.every((term) => haystack.includes(term))) return false;
|
|
349
|
+
}
|
|
350
|
+
if (filters.from || filters.to) {
|
|
351
|
+
const timing = meetingTiming(entry);
|
|
352
|
+
if (!timing.date) return false;
|
|
353
|
+
const day = tokyoDateValue(timing.date);
|
|
354
|
+
if (filters.from && day < filters.from) return false;
|
|
355
|
+
if (filters.to && day > filters.to) return false;
|
|
356
|
+
}
|
|
357
|
+
if (filters.participant && !participantsOf(entry).includes(filters.participant)) return false;
|
|
358
|
+
if (filters.status && listState(entry)?.code !== filters.status) return false;
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
295
362
|
function idempotencyKey() {
|
|
296
363
|
const random = typeof globalThis.crypto?.randomUUID === 'function'
|
|
297
364
|
? globalThis.crypto.randomUUID()
|
|
@@ -570,7 +637,9 @@ export function createMeetingMinutesUI({
|
|
|
570
637
|
meetingLabel,
|
|
571
638
|
meetingConnections,
|
|
572
639
|
canCreateMeeting,
|
|
640
|
+
createUnavailableReason,
|
|
573
641
|
canEditMinutes,
|
|
642
|
+
hostFilters = [],
|
|
574
643
|
currentPrincipalId,
|
|
575
644
|
now = () => new Date(),
|
|
576
645
|
} = {}) {
|
|
@@ -590,6 +659,8 @@ export function createMeetingMinutesUI({
|
|
|
590
659
|
loading: false,
|
|
591
660
|
listError: null,
|
|
592
661
|
detailError: null,
|
|
662
|
+
filters: { ...EMPTY_FILTERS },
|
|
663
|
+
sort: SORTS[0].value,
|
|
593
664
|
// Which document is being edited or about to be withdrawn; both reset on every detail load.
|
|
594
665
|
editingMinutesId: null,
|
|
595
666
|
withdrawingMinutesId: null,
|
|
@@ -598,6 +669,7 @@ export function createMeetingMinutesUI({
|
|
|
598
669
|
};
|
|
599
670
|
let listRequestGeneration = 0;
|
|
600
671
|
let detailRequestGeneration = 0;
|
|
672
|
+
let createReasonSequence = 0;
|
|
601
673
|
let connectionsSlot = null;
|
|
602
674
|
let railProjectSlot = null;
|
|
603
675
|
let railProjectMeeting = null;
|
|
@@ -610,6 +682,10 @@ export function createMeetingMinutesUI({
|
|
|
610
682
|
makeElement(doc, 'p', { className: 'bb-minutes-lead', textContent: '新しい会議から読み、確認待ちを見つけて確認します。' }),
|
|
611
683
|
);
|
|
612
684
|
const status = makeElement(doc, 'p', { className: 'bb-minutes-status', attrs: { role: 'status', 'aria-live': 'polite' } });
|
|
685
|
+
const toolbar = makeElement(doc, 'div', {
|
|
686
|
+
className: 'bb-minutes-toolbar',
|
|
687
|
+
attrs: { role: 'search', 'aria-label': '議事録の絞り込みと並べ替え' },
|
|
688
|
+
});
|
|
613
689
|
const columns = makeElement(doc, 'div', { className: 'bb-minutes-columns' });
|
|
614
690
|
const listPane = makeElement(doc, 'section', {
|
|
615
691
|
className: 'bb-minutes-list-pane',
|
|
@@ -617,9 +693,127 @@ export function createMeetingMinutesUI({
|
|
|
617
693
|
});
|
|
618
694
|
const detailPane = makeElement(doc, 'section', { className: 'bb-minutes-detail-pane', attrs: { 'aria-label': '議事録詳細' } });
|
|
619
695
|
columns.append(listPane, detailPane);
|
|
620
|
-
screen.append(heading, status, columns);
|
|
696
|
+
screen.append(heading, status, toolbar, columns);
|
|
621
697
|
root.replaceChildren(screen);
|
|
622
698
|
|
|
699
|
+
function filterField(label, control, className = '') {
|
|
700
|
+
const field = makeElement(doc, 'div', { className: `bb-minutes-filter-field${className ? ` ${className}` : ''}` });
|
|
701
|
+
field.append(makeElement(doc, 'span', { className: 'bb-minutes-label', textContent: label }), control);
|
|
702
|
+
return field;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function filterCombobox(label, options, value, onChange) {
|
|
706
|
+
return workspaceCombobox(doc, { label, options, value, className: 'bb-minutes-filter-combobox', onChange });
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const keyword = makeElement(doc, 'input', {
|
|
710
|
+
className: 'bb-minutes-input bb-minutes-filter-keyword',
|
|
711
|
+
type: 'search',
|
|
712
|
+
attrs: { 'aria-label': '会議名・議事録名で絞り込む', placeholder: '会議名・議事録名', autocomplete: 'off' },
|
|
713
|
+
});
|
|
714
|
+
keyword.addEventListener('input', () => { state.filters.query = keyword.value; renderList(); });
|
|
715
|
+
const fromInput = makeElement(doc, 'input', { className: 'bb-minutes-input', type: 'date', attrs: { 'aria-label': '期間の始め' } });
|
|
716
|
+
const toInput = makeElement(doc, 'input', { className: 'bb-minutes-input', type: 'date', attrs: { 'aria-label': '期間の終わり' } });
|
|
717
|
+
fromInput.addEventListener('change', () => { state.filters.from = fromInput.value; renderList(); });
|
|
718
|
+
toInput.addEventListener('change', () => { state.filters.to = toInput.value; renderList(); });
|
|
719
|
+
const period = makeElement(doc, 'span', { className: 'bb-minutes-filter-period' });
|
|
720
|
+
period.append(fromInput, makeElement(doc, 'span', { className: 'bb-minutes-filter-period-sep', textContent: '〜', attrs: { 'aria-hidden': 'true' } }), toInput);
|
|
721
|
+
const participantBox = filterCombobox('参加者', [{ value: '', label: 'すべての参加者' }], '', (value) => { state.filters.participant = value; renderList(); });
|
|
722
|
+
const statusBox = filterCombobox('確認の状態', STATE_FILTERS, '', (value) => { state.filters.status = value; renderList(); });
|
|
723
|
+
const sortBox = filterCombobox('並べ替え', SORTS, state.sort, (value) => { state.sort = value; renderList(); });
|
|
724
|
+
const hostSlot = makeElement(doc, 'div', { className: 'bb-minutes-filter-host' });
|
|
725
|
+
const participantField = filterField('参加者', participantBox.root);
|
|
726
|
+
const statusField = filterField('確認の状態', statusBox.root);
|
|
727
|
+
const reset = button(doc, '条件を外す', () => clearFilters(), { className: 'bb-minutes-button bb-minutes-filter-reset' });
|
|
728
|
+
toolbar.append(
|
|
729
|
+
filterField('キーワード', keyword, 'is-keyword'),
|
|
730
|
+
hostSlot,
|
|
731
|
+
filterField('期間', period, 'is-period'),
|
|
732
|
+
participantField,
|
|
733
|
+
statusField,
|
|
734
|
+
filterField('並べ替え', sortBox.root),
|
|
735
|
+
reset,
|
|
736
|
+
);
|
|
737
|
+
const hostBoxes = new Map();
|
|
738
|
+
|
|
739
|
+
function currentHostFilters() {
|
|
740
|
+
let filters = hostFilters;
|
|
741
|
+
if (typeof filters === 'function') {
|
|
742
|
+
try { filters = filters(); } catch { filters = []; }
|
|
743
|
+
}
|
|
744
|
+
return (Array.isArray(filters) ? filters : []).filter((filter) => isRecord(filter) && text(filter.id));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function clientFiltersActive() {
|
|
748
|
+
return Object.keys(EMPTY_FILTERS).some((key) => Boolean(state.filters[key]));
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function filtersActive() {
|
|
752
|
+
return clientFiltersActive() || currentHostFilters().some((filter) => text(filter.value));
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function clearFilters() {
|
|
756
|
+
state.filters = { ...EMPTY_FILTERS };
|
|
757
|
+
keyword.value = '';
|
|
758
|
+
fromInput.value = '';
|
|
759
|
+
toInput.value = '';
|
|
760
|
+
for (const filter of currentHostFilters()) {
|
|
761
|
+
if (text(filter.value) && typeof filter.onChange === 'function') filter.onChange('');
|
|
762
|
+
}
|
|
763
|
+
renderList();
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function renderToolbar(meetings) {
|
|
767
|
+
const hosts = currentHostFilters();
|
|
768
|
+
const seen = new Set();
|
|
769
|
+
for (const filter of hosts) {
|
|
770
|
+
const id = text(filter.id);
|
|
771
|
+
seen.add(id);
|
|
772
|
+
const options = Array.isArray(filter.options) ? filter.options : [];
|
|
773
|
+
const value = text(filter.value);
|
|
774
|
+
let entry = hostBoxes.get(id);
|
|
775
|
+
if (!entry) {
|
|
776
|
+
const box = workspaceCombobox(doc, {
|
|
777
|
+
label: text(filter.label, id),
|
|
778
|
+
options,
|
|
779
|
+
value,
|
|
780
|
+
placeholder: text(filter.placeholder, '選択してください'),
|
|
781
|
+
className: 'bb-minutes-filter-combobox',
|
|
782
|
+
onChange: (next) => {
|
|
783
|
+
const current = currentHostFilters().find((candidate) => text(candidate.id) === id);
|
|
784
|
+
if (typeof current?.onChange === 'function') current.onChange(next);
|
|
785
|
+
},
|
|
786
|
+
});
|
|
787
|
+
entry = { box, field: filterField(text(filter.label, id), box.root) };
|
|
788
|
+
hostBoxes.set(id, entry);
|
|
789
|
+
}
|
|
790
|
+
entry.box.update({ options, value, disabled: filter.disabled === true });
|
|
791
|
+
}
|
|
792
|
+
for (const [id, entry] of hostBoxes) {
|
|
793
|
+
if (seen.has(id)) continue;
|
|
794
|
+
entry.box.destroy();
|
|
795
|
+
hostBoxes.delete(id);
|
|
796
|
+
}
|
|
797
|
+
hostSlot.replaceChildren(...hosts.map((filter) => hostBoxes.get(text(filter.id)).field));
|
|
798
|
+
hostSlot.hidden = hosts.length === 0;
|
|
799
|
+
|
|
800
|
+
// Offer only the conditions the loaded records can answer (P9).
|
|
801
|
+
const participants = [...new Set(meetings.flatMap(participantsOf))]
|
|
802
|
+
.sort((left, right) => left.localeCompare(right, 'ja', { numeric: true }));
|
|
803
|
+
if (state.filters.participant && !participants.includes(state.filters.participant)) state.filters.participant = '';
|
|
804
|
+
participantBox.update({
|
|
805
|
+
options: [{ value: '', label: 'すべての参加者' }, ...participants.map((participant) => ({ value: participant, label: participant }))],
|
|
806
|
+
value: state.filters.participant,
|
|
807
|
+
});
|
|
808
|
+
participantField.hidden = participants.length === 0;
|
|
809
|
+
const hasStates = meetings.some((entry) => listState(entry));
|
|
810
|
+
if (!hasStates) state.filters.status = '';
|
|
811
|
+
statusBox.update({ value: state.filters.status });
|
|
812
|
+
statusField.hidden = !hasStates;
|
|
813
|
+
sortBox.update({ value: state.sort });
|
|
814
|
+
reset.hidden = !filtersActive();
|
|
815
|
+
}
|
|
816
|
+
|
|
623
817
|
function clock() {
|
|
624
818
|
try {
|
|
625
819
|
const value = typeof now === 'function' ? now() : new Date();
|
|
@@ -673,6 +867,34 @@ export function createMeetingMinutesUI({
|
|
|
673
867
|
return canCreateMeeting !== false;
|
|
674
868
|
}
|
|
675
869
|
|
|
870
|
+
/** One short reason from the host, shown beside a create button that cannot be pressed. */
|
|
871
|
+
function createReason() {
|
|
872
|
+
if (typeof createUnavailableReason === 'function') {
|
|
873
|
+
try {
|
|
874
|
+
return text(createUnavailableReason());
|
|
875
|
+
} catch {
|
|
876
|
+
return '';
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
return text(createUnavailableReason);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function appendCreateUnavailable(parent) {
|
|
883
|
+
const reason = createReason();
|
|
884
|
+
if (!reason) return;
|
|
885
|
+
const id = `bb-minutes-create-reason-${++createReasonSequence}`;
|
|
886
|
+
parent.append(
|
|
887
|
+
makeElement(doc, 'button', {
|
|
888
|
+
className: 'bb-minutes-button is-primary',
|
|
889
|
+
textContent: '会議を作成',
|
|
890
|
+
type: 'button',
|
|
891
|
+
disabled: true,
|
|
892
|
+
attrs: { type: 'button', 'aria-describedby': id },
|
|
893
|
+
}),
|
|
894
|
+
makeElement(doc, 'span', { className: 'bb-minutes-create-reason', textContent: reason, attrs: { id } }),
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
676
898
|
function canEdit() {
|
|
677
899
|
if (typeof canEditMinutes === 'function') {
|
|
678
900
|
try {
|
|
@@ -840,7 +1062,7 @@ export function createMeetingMinutesUI({
|
|
|
840
1062
|
parent.append(notice);
|
|
841
1063
|
}
|
|
842
1064
|
|
|
843
|
-
function renderMeetingRow(entry) {
|
|
1065
|
+
function renderMeetingRow(entry, { flat = false } = {}) {
|
|
844
1066
|
const meeting = entry.meeting;
|
|
845
1067
|
const timing = meetingTiming(entry);
|
|
846
1068
|
const item = makeElement(doc, 'button', {
|
|
@@ -854,10 +1076,19 @@ export function createMeetingMinutesUI({
|
|
|
854
1076
|
}));
|
|
855
1077
|
const line = makeElement(doc, 'span', { className: 'bb-minutes-item-line' });
|
|
856
1078
|
if (timing.kind === 'scheduled') {
|
|
857
|
-
|
|
1079
|
+
// Without date headings the row carries its own date.
|
|
1080
|
+
const when = flat ? `${formatMonthDay(timing.date)} ${formatClock(timing.date)}` : formatClock(timing.date);
|
|
1081
|
+
appendLinePart(line, makeElement(doc, 'span', { textContent: when }));
|
|
858
1082
|
} else if (timing.kind === 'fallback' || (timing.kind === 'unregistered' && timing.date)) {
|
|
859
1083
|
appendLinePart(line, makeElement(doc, 'span', { textContent: unscheduledLabel(timing) }));
|
|
860
1084
|
}
|
|
1085
|
+
if (flat) {
|
|
1086
|
+
const updated = updatedTime(entry);
|
|
1087
|
+
appendLinePart(line, makeElement(doc, 'span', {
|
|
1088
|
+
className: 'bb-minutes-item-updated',
|
|
1089
|
+
textContent: updated ? `更新 ${formatMonthDay(updated)} ${formatClock(updated)}` : '更新日時は未記録',
|
|
1090
|
+
}));
|
|
1091
|
+
}
|
|
861
1092
|
const label = meetingLabelText(meetingLabel, meeting);
|
|
862
1093
|
if (label) appendLinePart(line, makeElement(doc, 'span', { className: 'bb-minutes-meeting-label', textContent: label }));
|
|
863
1094
|
const rowState = listState(entry);
|
|
@@ -875,17 +1106,19 @@ export function createMeetingMinutesUI({
|
|
|
875
1106
|
clear(listPane);
|
|
876
1107
|
const head = makeElement(doc, 'div', { className: 'bb-minutes-pane-head' });
|
|
877
1108
|
const meetings = state.meetings.filter((entry) => isRecord(entry?.meeting) && typeof entry.meeting.meeting_id === 'string');
|
|
1109
|
+
renderToolbar(meetings);
|
|
1110
|
+
const visible = sortMeetings(meetings.filter((entry) => matchesFilters(entry, state.filters)), state.sort);
|
|
878
1111
|
if (canCreate()) {
|
|
879
1112
|
head.append(button(doc, '会議を作成', () => renderCreateForm(), { className: 'bb-minutes-button is-primary' }));
|
|
1113
|
+
} else {
|
|
1114
|
+
appendCreateUnavailable(head);
|
|
880
1115
|
}
|
|
881
|
-
|
|
1116
|
+
const narrowed = clientFiltersActive();
|
|
1117
|
+
head.append(makeElement(doc, 'p', {
|
|
1118
|
+
className: 'bb-minutes-sort-note',
|
|
1119
|
+
textContent: narrowed ? `${meetings.length}件中${visible.length}件` : `${meetings.length}件`,
|
|
1120
|
+
}));
|
|
882
1121
|
listPane.append(head);
|
|
883
|
-
if (!canCreate()) {
|
|
884
|
-
listPane.append(makeElement(doc, 'p', {
|
|
885
|
-
className: 'bb-minutes-create-guard',
|
|
886
|
-
textContent: '会議を作成する権限がありません。作成はプロジェクトの編集権限がある人が行います。',
|
|
887
|
-
}));
|
|
888
|
-
}
|
|
889
1122
|
if (state.listError) {
|
|
890
1123
|
appendLoadError(listPane, `会議一覧を読み込めませんでした。理由: ${state.listError}`, () => loadMeetings({ restoreFocus: true }));
|
|
891
1124
|
}
|
|
@@ -893,14 +1126,27 @@ export function createMeetingMinutesUI({
|
|
|
893
1126
|
if (state.listError) return;
|
|
894
1127
|
listPane.append(makeElement(doc, 'p', {
|
|
895
1128
|
className: 'bb-minutes-empty',
|
|
896
|
-
textContent:
|
|
1129
|
+
textContent: filtersActive()
|
|
1130
|
+
? '条件に合う会議はありません。'
|
|
1131
|
+
: 'まだ議事録はありません。会議を取り込むと、ここに新しい順で並びます。',
|
|
897
1132
|
}));
|
|
1133
|
+
if (filtersActive()) listPane.append(button(doc, '条件を外す', () => clearFilters()));
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (visible.length === 0) {
|
|
1137
|
+
listPane.append(makeElement(doc, 'p', { className: 'bb-minutes-empty', textContent: '条件に合う会議はありません。' }));
|
|
1138
|
+
listPane.append(button(doc, '条件を外す', () => clearFilters()));
|
|
898
1139
|
return;
|
|
899
1140
|
}
|
|
900
1141
|
const list = makeElement(doc, 'div', { className: 'bb-minutes-meeting-list' });
|
|
1142
|
+
if (state.sort === 'updated-desc') {
|
|
1143
|
+
for (const entry of visible) list.append(renderMeetingRow(entry, { flat: true }));
|
|
1144
|
+
listPane.append(list);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
901
1147
|
const today = clock();
|
|
902
1148
|
let currentKey = null;
|
|
903
|
-
for (const entry of
|
|
1149
|
+
for (const entry of visible) {
|
|
904
1150
|
const timing = meetingTiming(entry);
|
|
905
1151
|
if (timing.kind === 'unregistered') continue;
|
|
906
1152
|
const key = dateKey(timing.date);
|
|
@@ -913,7 +1159,7 @@ export function createMeetingMinutesUI({
|
|
|
913
1159
|
}
|
|
914
1160
|
list.append(renderMeetingRow(entry));
|
|
915
1161
|
}
|
|
916
|
-
const unregistered =
|
|
1162
|
+
const unregistered = visible.filter((entry) => meetingTiming(entry).kind === 'unregistered');
|
|
917
1163
|
if (unregistered.length) {
|
|
918
1164
|
list.append(makeElement(doc, 'h3', {
|
|
919
1165
|
className: 'bb-minutes-date-heading',
|
|
@@ -1259,14 +1505,17 @@ export function createMeetingMinutesUI({
|
|
|
1259
1505
|
}
|
|
1260
1506
|
}
|
|
1261
1507
|
|
|
1508
|
+
function renderCreateUnavailable() {
|
|
1509
|
+
clear(detailPane);
|
|
1510
|
+
detailPane.append(makeElement(doc, 'p', { className: 'bb-minutes-empty', textContent: '議事録を選ぶと、ここに本文が出ます。' }));
|
|
1511
|
+
const reason = createReason();
|
|
1512
|
+
if (reason) detailPane.append(makeElement(doc, 'p', { className: 'bb-minutes-create-reason', textContent: reason }));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1262
1515
|
function renderCreateForm() {
|
|
1263
1516
|
clear(detailPane);
|
|
1264
1517
|
if (!canCreate()) {
|
|
1265
|
-
|
|
1266
|
-
detailPane.append(makeElement(doc, 'p', {
|
|
1267
|
-
className: 'bb-minutes-create-guard',
|
|
1268
|
-
textContent: '会議を作成する権限がありません。作成はプロジェクトの編集権限がある人が行います。',
|
|
1269
|
-
}));
|
|
1518
|
+
renderCreateUnavailable();
|
|
1270
1519
|
return;
|
|
1271
1520
|
}
|
|
1272
1521
|
const form = makeElement(doc, 'form', { className: 'bb-minutes-create-form' });
|
|
@@ -1284,12 +1533,7 @@ export function createMeetingMinutesUI({
|
|
|
1284
1533
|
if (!canCreate()) {
|
|
1285
1534
|
save.disabled = true;
|
|
1286
1535
|
renderList();
|
|
1287
|
-
|
|
1288
|
-
detailPane.append(makeElement(doc, 'p', { className: 'bb-minutes-empty', textContent: '議事録を選ぶと、ここに本文が出ます。' }));
|
|
1289
|
-
detailPane.append(makeElement(doc, 'p', {
|
|
1290
|
-
className: 'bb-minutes-create-guard',
|
|
1291
|
-
textContent: '会議を作成する権限がありません。作成はプロジェクトの編集権限がある人が行います。',
|
|
1292
|
-
}));
|
|
1536
|
+
renderCreateUnavailable();
|
|
1293
1537
|
return;
|
|
1294
1538
|
}
|
|
1295
1539
|
save.disabled = true;
|
|
@@ -1403,6 +1647,7 @@ export function createMeetingMinutesUI({
|
|
|
1403
1647
|
return state.detail;
|
|
1404
1648
|
}
|
|
1405
1649
|
|
|
1650
|
+
renderToolbar([]);
|
|
1406
1651
|
renderDetail(null);
|
|
1407
1652
|
void loadMeetings();
|
|
1408
1653
|
return {
|
package/ui/outcome-knowledge.js
CHANGED
|
@@ -84,6 +84,9 @@ const SCOPE_LABELS = Object.freeze({
|
|
|
84
84
|
|
|
85
85
|
const LIFECYCLE_STATUS_LABELS = Object.freeze({
|
|
86
86
|
active: '有効',
|
|
87
|
+
decided: '決定済み',
|
|
88
|
+
active_reference: '参照用',
|
|
89
|
+
unknown: UNKNOWN,
|
|
87
90
|
inactive: '無効',
|
|
88
91
|
retired: '失効',
|
|
89
92
|
superseded: '置換済み',
|
|
@@ -1480,7 +1483,8 @@ export function createKnowledgeOverviewController(options = {}) {
|
|
|
1480
1483
|
const { root, api, onSelectProject } = options;
|
|
1481
1484
|
if (!root || typeof root.replaceChildren !== 'function') throw new Error('knowledge_root_unavailable');
|
|
1482
1485
|
const labels = resolveKnowledgeUiLabels(options.labels);
|
|
1483
|
-
|
|
1486
|
+
// A host path may already carry conditions (for example `?limit=100`); keep them and add the filters.
|
|
1487
|
+
const [itemsBase, itemsBaseQuery = ''] = (optionalText(options.itemsPath).trim() || `/api/knowledge/${KNOWLEDGE_ROUTES.items}`).split(/\?(.*)/s);
|
|
1484
1488
|
const state = {
|
|
1485
1489
|
filters: { q: '', scope: 'all', status: 'active' },
|
|
1486
1490
|
list: { state: 'loading', records: null, absence_confirmed: false, searched_scope: null },
|
|
@@ -1491,7 +1495,7 @@ export function createKnowledgeOverviewController(options = {}) {
|
|
|
1491
1495
|
const projectName = (code) => projectOptions(options.projects).find((option) => option.code === code)?.name ?? null;
|
|
1492
1496
|
|
|
1493
1497
|
function itemsPath() {
|
|
1494
|
-
const query = new URLSearchParams();
|
|
1498
|
+
const query = new URLSearchParams(itemsBaseQuery);
|
|
1495
1499
|
if (state.filters.q) query.set('q', state.filters.q);
|
|
1496
1500
|
if (state.filters.scope) query.set('scope', state.filters.scope);
|
|
1497
1501
|
if (state.filters.status) query.set('status', state.filters.status);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 「人と会社」. Load after workspace-kit.css and graph-view-shared.css.
|
|
3
3
|
* Column templates of the people and company ledgers, the group titles,
|
|
4
|
-
* the
|
|
5
|
-
*
|
|
4
|
+
* the add buttons, the recorded company note, and a record's page (the way
|
|
5
|
+
* back, its head, the actions gathered under it and its relations), with a
|
|
6
|
+
* 760px variant.
|
|
6
7
|
* Uses only the shared tokens in brainbase-tokens.css.
|
|
7
8
|
*/
|
|
8
9
|
.bb-pc-people,
|
|
@@ -67,25 +68,12 @@
|
|
|
67
68
|
font-size: 12px;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
.bb-graph-rail .bb-pc-fix {
|
|
71
|
-
display: grid;
|
|
72
|
-
gap: 6px;
|
|
73
|
-
margin: 16px 0 0;
|
|
74
|
-
color: var(--bb-color-muted);
|
|
75
|
-
font-size: 12px;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.bb-pc-actions,
|
|
79
71
|
.bb-pc-relation-actions {
|
|
80
72
|
display: flex;
|
|
81
73
|
flex-wrap: wrap;
|
|
82
74
|
gap: 8px;
|
|
83
75
|
}
|
|
84
76
|
|
|
85
|
-
.bb-pc-actions {
|
|
86
|
-
margin: 0 0 12px;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
77
|
.bb-pc-company-cell {
|
|
90
78
|
display: grid;
|
|
91
79
|
gap: 2px;
|
|
@@ -102,6 +90,11 @@
|
|
|
102
90
|
}
|
|
103
91
|
|
|
104
92
|
@media (max-width: 760px) {
|
|
93
|
+
/* Below a host's top bar on a phone. */
|
|
94
|
+
.bb-pc-detail {
|
|
95
|
+
scroll-margin-top: 72px;
|
|
96
|
+
}
|
|
97
|
+
|
|
105
98
|
.bb-pc-people-ledger .bb-ws-ledger-row {
|
|
106
99
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 84px;
|
|
107
100
|
}
|
|
@@ -262,3 +255,161 @@
|
|
|
262
255
|
align-items: center;
|
|
263
256
|
min-height: 46px;
|
|
264
257
|
}
|
|
258
|
+
|
|
259
|
+
/* A record's page (2026-10-16). Choosing a row turns the workspace into the
|
|
260
|
+
record's page, as a CRM does: the way back with the record's place, the
|
|
261
|
+
head, then what can be done to it, gathered in one place with what the page
|
|
262
|
+
says about it (nothing is deleted; why an action is disabled). */
|
|
263
|
+
.bb-pc-action-group {
|
|
264
|
+
display: grid;
|
|
265
|
+
gap: 8px;
|
|
266
|
+
margin: 0 0 20px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.bb-pc-action-row {
|
|
270
|
+
display: flex;
|
|
271
|
+
flex-wrap: wrap;
|
|
272
|
+
gap: 8px;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.bb-pc-action-row .bb-ws-button.is-primary {
|
|
276
|
+
border-color: var(--bb-color-action);
|
|
277
|
+
background: var(--bb-color-action);
|
|
278
|
+
color: var(--bb-color-action-text);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.bb-pc-action-row .bb-ws-button.is-primary:hover:not(:disabled) {
|
|
282
|
+
border-color: var(--bb-color-action-hover);
|
|
283
|
+
background: var(--bb-color-action-hover);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.bb-pc-action-row .bb-ws-button[data-adds]::before {
|
|
287
|
+
content: '+';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.bb-pc-action-note,
|
|
291
|
+
.bb-pc-action-reason {
|
|
292
|
+
margin: 0;
|
|
293
|
+
color: var(--bb-color-muted);
|
|
294
|
+
font-size: 12px;
|
|
295
|
+
line-height: 1.6;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.bb-pc-action-reason {
|
|
299
|
+
justify-self: start;
|
|
300
|
+
padding: 4px 10px;
|
|
301
|
+
border-radius: 6px;
|
|
302
|
+
background: var(--bb-color-warning-soft);
|
|
303
|
+
color: var(--bb-color-warning);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.bb-pc-action-note a {
|
|
307
|
+
color: var(--bb-color-accent-strong);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.bb-pc-add {
|
|
311
|
+
margin-top: -4px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.bb-pc-detail {
|
|
315
|
+
display: grid;
|
|
316
|
+
gap: 0;
|
|
317
|
+
scroll-margin-top: 16px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.bb-pc-detail-nav {
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-wrap: wrap;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 6px;
|
|
325
|
+
margin: 0 0 20px;
|
|
326
|
+
padding-bottom: 12px;
|
|
327
|
+
border-bottom: 1px solid var(--bb-color-border);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.bb-pc-detail-nav .bb-pc-back {
|
|
331
|
+
margin-right: auto;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.bb-pc-detail-place {
|
|
335
|
+
display: inline-flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
gap: 6px;
|
|
338
|
+
margin-right: 4px;
|
|
339
|
+
color: var(--bb-color-muted);
|
|
340
|
+
font-size: 12px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.bb-pc-detail-nav .bb-ws-button[aria-label='前へ'],
|
|
344
|
+
.bb-pc-detail-nav .bb-ws-button[aria-label='次へ'] {
|
|
345
|
+
min-width: 32px;
|
|
346
|
+
padding-inline: 8px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.bb-pc-detail-head {
|
|
350
|
+
display: flex;
|
|
351
|
+
align-items: flex-start;
|
|
352
|
+
gap: 14px;
|
|
353
|
+
margin: 0 0 18px;
|
|
354
|
+
scroll-margin-top: 16px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.bb-pc-mark.bb-pc-detail-mark {
|
|
358
|
+
width: 44px;
|
|
359
|
+
height: 44px;
|
|
360
|
+
margin: 0;
|
|
361
|
+
font-size: 18px;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.bb-pc-company-mark.bb-pc-detail-mark {
|
|
365
|
+
border-radius: 10px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.bb-pc-detail-copy {
|
|
369
|
+
display: grid;
|
|
370
|
+
gap: 4px;
|
|
371
|
+
min-width: 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.bb-pc-detail-copy h2 {
|
|
375
|
+
margin: 0;
|
|
376
|
+
color: var(--bb-color-ink);
|
|
377
|
+
font-size: 22px;
|
|
378
|
+
line-height: 1.3;
|
|
379
|
+
overflow-wrap: anywhere;
|
|
380
|
+
scroll-margin-top: 16px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.bb-pc-detail-copy h2:focus {
|
|
384
|
+
outline: none;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.bb-pc-detail-sub {
|
|
388
|
+
margin: 0;
|
|
389
|
+
color: var(--bb-color-muted);
|
|
390
|
+
font-size: 13px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.bb-pc-detail-lead {
|
|
394
|
+
margin: 4px 0 0;
|
|
395
|
+
color: var(--bb-color-ink);
|
|
396
|
+
font-size: 13px;
|
|
397
|
+
line-height: 1.6;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.bb-pc-detail-actions {
|
|
401
|
+
padding: 14px 16px;
|
|
402
|
+
border: 1px solid var(--bb-color-border);
|
|
403
|
+
border-radius: 10px;
|
|
404
|
+
background: var(--bb-color-canvas);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.bb-pc-detail-block {
|
|
408
|
+
margin: 0 0 22px;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.bb-pc-detail-block h3 {
|
|
412
|
+
margin: 0 0 6px;
|
|
413
|
+
color: var(--bb-color-ink);
|
|
414
|
+
font-size: 13px;
|
|
415
|
+
}
|