Overview
Comment:Add quick search to accounts lists for quickly finding accounts
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | accounts_refactor
Files: files | file ages | folders
SHA3-256: 5b2dfdefd4ce99747f973080ef9f3e8796a0d8ab1d63f8244aeaace900819286
User & Date: bohwaz on 2022-11-05 16:46:33
Other Links: branch diff | manifest | tags
Context
2022-11-05
16:52
Pre-select asset or liability, to get a sensible default check-in: 91144d5c10 user: bohwaz tags: accounts_refactor
16:46
Add quick search to accounts lists for quickly finding accounts check-in: 5b2dfdefd4 user: bohwaz tags: accounts_refactor
05:08
Fix migration of projects check-in: 86b60c055e user: bohwaz tags: accounts_refactor
Changes

Modified src/include/migrations/1.1/32.sql from [43af03b5d1] to [a7eb3b4440].

21
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36
	SET id_project = (SELECT b.id FROM acc_projects AS b INNER JOIN acc_accounts_old c ON c.code = b.code WHERE c.id = a.id_project)
	WHERE id_project IS NOT NULL;

UPDATE acc_transactions_lines AS a
	SET id_project = (SELECT b.id FROM acc_projects AS b INNER JOIN acc_accounts_old c ON c.code = b.code WHERE c.id = a.id_project)
	WHERE id_project IS NOT NULL;

-- Remove first 9 from code (added in 1.1.30)

UPDATE acc_projects SET code = CASE WHEN SUBSTR(code, 1, 1) = '9' THEN SUBSTR(code, 2) ELSE code END;

--UPDATE acc_transactions_lines SET id_project = NULL WHERE id_project NOT IN (SELECT id FROM acc_projects);
--UPDATE acc_transactions_lines SET id_project = 424242 WHERE id_project IS NULL;

INSERT INTO acc_accounts SELECT *, CASE WHEN type > 0 AND type <= 8 THEN 1 ELSE 0 END FROM acc_accounts_old;

-- Delete old analytical accounts







|
>
|







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
	SET id_project = (SELECT b.id FROM acc_projects AS b INNER JOIN acc_accounts_old c ON c.code = b.code WHERE c.id = a.id_project)
	WHERE id_project IS NOT NULL;

UPDATE acc_transactions_lines AS a
	SET id_project = (SELECT b.id FROM acc_projects AS b INNER JOIN acc_accounts_old c ON c.code = b.code WHERE c.id = a.id_project)
	WHERE id_project IS NOT NULL;

-- Remove first 99 and 9 from code (added in 1.1.30)
UPDATE acc_projects SET code = CASE WHEN SUBSTR(code, 1, 2) = '99' AND LENGTH(code) > 2 THEN SUBSTR(code, 3) ELSE code END;
UPDATE acc_projects SET code = CASE WHEN SUBSTR(code, 1, 1) = '9' AND LENGTH(code) > 1 THEN SUBSTR(code, 2) ELSE code END;

--UPDATE acc_transactions_lines SET id_project = NULL WHERE id_project NOT IN (SELECT id FROM acc_projects);
--UPDATE acc_transactions_lines SET id_project = 424242 WHERE id_project IS NULL;

INSERT INTO acc_accounts SELECT *, CASE WHEN type > 0 AND type <= 8 THEN 1 ELSE 0 END FROM acc_accounts_old;

-- Delete old analytical accounts

Modified src/templates/acc/charts/accounts/all.tpl from [f98b58b8c2] to [cab3c3255e].

1
2
3
4







5
6
7
8
9
10
11
12
13
14
15
16
{include file="admin/_head.tpl" title=$chart.label current="acc/years"}

{include file="acc/charts/accounts/_nav.tpl" current="all"}








<p class="help">
	Les comptes marqués comme «&nbsp;<em>Ajouté</em>&nbsp;» ont été ajoutés au plan comptable officiel par vous-même.
</p>

<form method="post" action="{$self_url}">

{include file="common/dynamic_list_head.tpl"}

	{foreach from=$list->iterate() item="account"}
		<tr class="account account-level-{$account.level}">
			<td class="num">{$account.code}</td>
			<th{if !$account.description} colspan=2{/if}>{$account.label}</th>




>
>
>
>
>
>
>
|
|
|
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14


15
16
17
18
19
20
21
{include file="admin/_head.tpl" title=$chart.label current="acc/years"}

{include file="acc/charts/accounts/_nav.tpl" current="all"}

<form method="post" action="{$self_url}" data-focus="1">

	<p class="actions quick-search">
		<input type="text" placeholder="Recherche rapide…" title="Filtrer la liste" />{button shape="delete" type="reset" title="Effacer la recherche"}
		{* We can't use input type="search" because Firefox sucks *}
	</p>

	<p class="help">
		Les comptes marqués comme «&nbsp;<em>Ajouté</em>&nbsp;» ont été ajoutés au plan comptable officiel par vous-même.
	</p>



{include file="common/dynamic_list_head.tpl"}

	{foreach from=$list->iterate() item="account"}
		<tr class="account account-level-{$account.level}">
			<td class="num">{$account.code}</td>
			<th{if !$account.description} colspan=2{/if}>{$account.label}</th>
36
37
38
39
40
41
42
43
44
45
46
47
				{/if}
			</td>
		</tr>
	{/foreach}
	</tbody>
</table>

<script type="text/javascript" src="{$admin_url}static/scripts/accounts_bookmark.js"></script>

</form>

{include file="admin/_foot.tpl"}







|




41
42
43
44
45
46
47
48
49
50
51
52
				{/if}
			</td>
		</tr>
	{/foreach}
	</tbody>
</table>

<script type="text/javascript" src="{$admin_url}static/scripts/accounts_list.js"></script>

</form>

{include file="admin/_foot.tpl"}

Modified src/templates/acc/charts/accounts/index.tpl from [af532ac7cc] to [5d942236c6].

1
2
3
4








5
6
7
8
9
10
11
12
13
14
15
16
{include file="admin/_head.tpl" title=$chart.label current="acc/years"}

{include file="acc/charts/accounts/_nav.tpl" current="favorites"}









<p class="help">
	Cette liste regroupe les comptes de banque, caisse, attente, tiers, dépense, recette ou bénévolat qui sont soit marqués comme favori, soit ajoutés manuellement, soit déjà utilisés dans un exercice.
</p>

<form method="post" action="all.php?id={$chart.id}">

<table class="list">
{foreach from=$accounts_grouped item="group"}
	<tbody>
		<tr>
			<td colspan="4"><h2 class="ruler">{$group.label}</h2></td>
			<td class="actions">




>
>
>
>
>
>
>
>
|
|
|
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


16
17
18
19
20
21
22
{include file="admin/_head.tpl" title=$chart.label current="acc/years"}

{include file="acc/charts/accounts/_nav.tpl" current="favorites"}

<form method="post" action="all.php?id={$chart.id}">

	<p class="actions quick-search">
		<input type="text" placeholder="Recherche rapide…" title="Filtrer la liste" />{button shape="delete" type="reset" title="Effacer la recherche"}
		{* We can't use input type="search" because Firefox sucks *}
	</p>


	<p class="help">
		Cette liste regroupe les comptes de banque, caisse, attente, tiers, dépense, recette ou bénévolat qui sont soit marqués comme favori, soit ajoutés manuellement, soit déjà utilisés dans un exercice.
	</p>



<table class="list">
{foreach from=$accounts_grouped item="group"}
	<tbody>
		<tr>
			<td colspan="4"><h2 class="ruler">{$group.label}</h2></td>
			<td class="actions">
42
43
44
45
46
47
48
49

50
51
52
			</td>
		</tr>
	{/foreach}
	</tbody>
{/foreach}
</table>

<script type="text/javascript" src="{$admin_url}static/scripts/accounts_bookmark.js"></script>

</form>

{include file="admin/_foot.tpl"}







|
>



48
49
50
51
52
53
54
55
56
57
58
59
			</td>
		</tr>
	{/foreach}
	</tbody>
{/foreach}
</table>

<script type="text/javascript" src="{$admin_url}static/scripts/accounts_list.js"></script>

</form>

{include file="admin/_foot.tpl"}

Modified src/templates/acc/charts/accounts/selector.tpl from [aa02383c7a] to [1be1c0e24c].

1
2
3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
{include file="admin/_head.tpl" title="Sélectionner un compte"}

<div class="selector">

{if empty($grouped_accounts) && empty($accounts)}
	<p class="block alert">Le plan comptable ne comporte aucun compte de ce type.<br />
		{linkbutton href="!acc/charts/accounts/new.php?id=%s&type=%s"|args:$chart.id,$targets[0] label="Créer un compte" shape="plus"}
	</p>

{else}

	<header>
		<h2>
			<input type="text" placeholder="Recherche rapide" id="lookup" />

		</h2>

		{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
			<?php $page = isset($grouped_accounts) ? '' : 'all.php'; ?>
			<p class="edit">{linkbutton label="Modifier les comptes" href="!acc/charts/accounts/%s?id=%d&types=%s"|args:$page,$chart.id,$targets_str shape="edit"}</aside></p>
		{/if}













|
|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{include file="admin/_head.tpl" title="Sélectionner un compte"}

<div class="selector">

{if empty($grouped_accounts) && empty($accounts)}
	<p class="block alert">Le plan comptable ne comporte aucun compte de ce type.<br />
		{linkbutton href="!acc/charts/accounts/new.php?id=%s&type=%s"|args:$chart.id,$targets[0] label="Créer un compte" shape="plus"}
	</p>

{else}

	<header>
		<h2 class="quick-search">
			<input type="text" placeholder="Recherche rapide" title="Filtrer la liste" />{button shape="delete" type="reset" title="Effacer la recherche"}
			{* We can't use input type="search" because Firefox sucks *}
		</h2>

		{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
			<?php $page = isset($grouped_accounts) ? '' : 'all.php'; ?>
			<p class="edit">{linkbutton label="Modifier les comptes" href="!acc/charts/accounts/%s?id=%d&types=%s"|args:$page,$chart.id,$targets_str shape="edit"}</aside></p>
		{/if}

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
				{foreach from=$group.accounts item="account"}
					<tr data-idx="{$index}" class="account">
						<td class="bookmark">{if $account.bookmark}{icon shape="star" title="Compte favori"}{/if}</td>
						<td class="num">{$account.code}</td>
						<th>{$account.label}</th>
						<td class="desc">{$account.description}</td>
						<td class="actions">
							<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
						</td>
					</tr>
					<?php $index++; ?>
				{/foreach}
				</tbody>
			</table>
		{/foreach}

	{else}

		<table class="list">
			<tbody>
			{foreach from=$accounts item="account"}
				<tr data-idx="{$iteration}" class="account account-level-{$account->level()}">
					<td class="bookmark">{if $account.bookmark}{icon shape="star" title="Compte favori"}{/if}</td>
					<td class="num">{$account.code}</td>
					<th>{$account.label}</th>
					<td class="actions">
						<button class="icn-btn" value="{$account.id}" data-label="{$account.code} — {$account.label}" data-icon="&rarr;">Sélectionner</button>
					</td>
				</tr>
			{/foreach}
			</tbody>
		</table>

	{/if}







|


















|







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
				{foreach from=$group.accounts item="account"}
					<tr data-idx="{$index}" class="account">
						<td class="bookmark">{if $account.bookmark}{icon shape="star" title="Compte favori"}{/if}</td>
						<td class="num">{$account.code}</td>
						<th>{$account.label}</th>
						<td class="desc">{$account.description}</td>
						<td class="actions">
							{button shape="right" value=$account.id data-label="%s — %s"|args:$account.code,$account.label label="Sélectionner"}
						</td>
					</tr>
					<?php $index++; ?>
				{/foreach}
				</tbody>
			</table>
		{/foreach}

	{else}

		<table class="list">
			<tbody>
			{foreach from=$accounts item="account"}
				<tr data-idx="{$iteration}" class="account account-level-{$account->level()}">
					<td class="bookmark">{if $account.bookmark}{icon shape="star" title="Compte favori"}{/if}</td>
					<td class="num">{$account.code}</td>
					<th>{$account.label}</th>
					<td class="actions">
						{button shape="right" value=$account.id data-label="%s — %s"|args:$account.code,$account.label label="Sélectionner"}
					</td>
				</tr>
			{/foreach}
			</tbody>
		</table>

	{/if}

Modified src/www/admin/static/scripts/accounts_list.js from [ac8766f7de] to [590fc24309].

1
2
3
4
5
6
7
8
9
10
11
12










































$('button[name]').forEach((b) => {
	b.onclick = () => {
		b.value = parseInt(b.value) ? 0 : 1;
		b.setAttribute('data-icon', b.value == 1 ? '☑' : '☐');
		fetch(document.forms[0].action, {
			'method': 'POST',
			'headers': {"Content-Type": "application/x-www-form-urlencoded"},
			'body': b.name + '=' + b.value
		});
		return false;
	};
});










































|











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$('button[name*=bookmark]').forEach((b) => {
	b.onclick = () => {
		b.value = parseInt(b.value) ? 0 : 1;
		b.setAttribute('data-icon', b.value == 1 ? '☑' : '☐');
		fetch(document.forms[0].action, {
			'method': 'POST',
			'headers': {"Content-Type": "application/x-www-form-urlencoded"},
			'body': b.name + '=' + b.value
		});
		return false;
	};
});

RegExp.escape = function(string) {
  return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};

function normalizeString(str) {
	return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
}

var q = document.querySelector('.quick-search input[type=text]');

if (q) {
	var rows = document.querySelectorAll('table tr.account');

	rows.forEach((e, k) => {
		var l = e.querySelector('td.num').innerText + ' ' + e.querySelector('th').innerText;
		e.setAttribute('data-search-label', normalizeString(l));
	});

	q.addEventListener('keyup', filterTableList);
	document.querySelector('.quick-search button[type=reset]').onclick = () => {
		q.value = '';
		q.focus();
		return filterTableList();
	};
	q.focus();
}

function filterTableList() {
	var query = new RegExp(RegExp.escape(normalizeString(q.value)), 'i');

	rows.forEach((elm) => {
		if (elm.getAttribute('data-search-label').match(query)) {
			g.toggle(elm, true);
		}
		else {
			g.toggle(elm, false);
		}
	});

	return false;
}

Modified src/www/admin/static/scripts/selector.js from [eeca799734] to [5f77069dce].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RegExp.escape = function(string) {
  return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};

function normalizeString(str) {
	return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
}

var buttons = document.querySelectorAll('button');

buttons.forEach((e) => {
	e.onclick = () => {
		window.parent.g.inputListSelected(e.value, e.getAttribute('data-label'));
	};
});









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RegExp.escape = function(string) {
  return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
};

function normalizeString(str) {
	return str.normalize('NFD').replace(/[\u0300-\u036f]/g, "")
}

var buttons = document.querySelectorAll('button[type=button]');

buttons.forEach((e) => {
	e.onclick = () => {
		window.parent.g.inputListSelected(e.value, e.getAttribute('data-label'));
	};
});

101
102
103
104
105
106
107
108

109
110
111










112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
	return false;
});

if (buttons[0]) {
	buttons[0].focus();
}

var q = document.getElementById('lookup');


if (q) {
	q.addEventListener('keyup', (e) => {










		var query = new RegExp(RegExp.escape(normalizeString(q.value)), 'i');

		rows.forEach((elm) => {
			if (elm.getAttribute('data-search-label').match(query)) {
				g.toggle(elm, true);
			}
			else {
				g.toggle(elm, false);
			}
		});

		if (first = document.querySelector('tbody tr:not(.hidden)')) {
			if (f = document.querySelector('tr.focused')) {
				f.classList.remove('focused');
			}
			first.classList.add('focused');
		}

		if (e.key == 'Enter') {
			if (first = document.querySelector('tbody tr.focused:not(.hidden) button')) {
				first.click();
			}
		}

		return false;
	});

	q.focus();
}

var o = document.getElementById('f_filter');

if (o) {
	o.onchange = () => {
		let s = new URLSearchParams(window.location.search);
		s.set("filter", o.value);
		window.location.search = s.toString();
	};
}







|
>

|
|
>
>
>
>
>
>
>
>
>
>
|

|
|
|
|
|
|
|
|

|
|
|
|
|
|

|
|
|
|
|

|
<
<
<











101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147



148
149
150
151
152
153
154
155
156
157
158
	return false;
});

if (buttons[0]) {
	buttons[0].focus();
}

var q = document.querySelector('.quick-search input[type=text]');
var qr = document.querySelector('.quick-search button[type=reset]');

if (q && qr) {
	q.addEventListener('keyup', filterTableList);
	qr.onclick = (e) => {
		q.value = '';
		q.focus();
		return filterTableList(e);
	};

	q.focus();
}

function filterTableList(e) {
	var query = new RegExp(RegExp.escape(normalizeString(q.value)), 'i');

	rows.forEach((elm) => {
		if (elm.getAttribute('data-search-label').match(query)) {
			g.toggle(elm, true);
		}
		else {
			g.toggle(elm, false);
		}
	});

	if (first = document.querySelector('tbody tr:not(.hidden)')) {
		if (f = document.querySelector('tr.focused')) {
			f.classList.remove('focused');
		}
		first.classList.add('focused');
	}

	if (e.key == 'Enter') {
		if (first = document.querySelector('tbody tr.focused:not(.hidden) button')) {
			first.click();
		}
	}

	return false;



}

var o = document.getElementById('f_filter');

if (o) {
	o.onchange = () => {
		let s = new URLSearchParams(window.location.search);
		s.set("filter", o.value);
		window.location.search = s.toString();
	};
}

Modified src/www/admin/static/styles/03-forms.css from [6a971c609b] to [aeaac26b8f].

431
432
433
434
435
436
437

438
439
440
441
442
443
444
dd.help.example {
    margin-left: 2.5em;
    font-size: .9em;
}

form p.actions {
    float: right;

}

/** Datepicker widget */
.datepicker-parent {
    position: relative;
}








>







431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
dd.help.example {
    margin-left: 2.5em;
    font-size: .9em;
}

form p.actions {
    float: right;
    margin: .5em;
}

/** Datepicker widget */
.datepicker-parent {
    position: relative;
}

711
712
713
714
715
716
717











718
719
720
721
722
723
724
    float: right;
    margin: 0;
}

.selector header h2 input {
    width: calc(100% - 1em);
}












.selector select {
    font-size: .9em;
    margin: .2em 0;
    padding: .2em;
    border-color: var(--gLightBorderColor);
    cursor: pointer;







>
>
>
>
>
>
>
>
>
>
>







712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
    float: right;
    margin: 0;
}

.selector header h2 input {
    width: calc(100% - 1em);
}

.quick-search button[type=reset] {
    margin-left: -2.5em;
    background: none;
    border: none;
    color: var(--gBorderColor);
}

h2.quick-search button[type=reset] {
    font-size: 1rem;
}

.selector select {
    font-size: .9em;
    margin: .2em 0;
    padding: .2em;
    border-color: var(--gLightBorderColor);
    cursor: pointer;