|
@@ -9,13 +9,20 @@ from dateutil.parser import parse as parse_time
|
|
|
from dateutil.parser._parser import ParserError
|
|
|
from decimal import Decimal, InvalidOperation
|
|
|
from itertools import chain
|
|
|
-from typing import Callable, Union
|
|
|
+from typing import (
|
|
|
+ Callable,
|
|
|
+ Union,
|
|
|
+ Tuple,
|
|
|
+ List,
|
|
|
+ Iterable,
|
|
|
+)
|
|
|
from urwid import (
|
|
|
connect_signal,
|
|
|
AttrMap,
|
|
|
Button,
|
|
|
Columns,
|
|
|
Divider,
|
|
|
+ Edit,
|
|
|
Filler,
|
|
|
LineBox,
|
|
|
Padding,
|
|
@@ -37,6 +44,46 @@ from . import ActivityManager
|
|
|
from .Rating import Rating
|
|
|
from .NewProduct import NewProduct
|
|
|
|
|
|
+def to_numbered_field(x):
|
|
|
+ if len(x[0].split('#', 1)) > 1:
|
|
|
+ name, idx = x[0].split('#', 1)
|
|
|
+ idx = int(idx)
|
|
|
+ else:
|
|
|
+ name, idx = x[0], 0
|
|
|
+
|
|
|
+ return (name, int(idx)), x[1]
|
|
|
+
|
|
|
+def to_unnumbered_field(x):
|
|
|
+ return x[0][0], x[1]
|
|
|
+
|
|
|
+def in_same_row(name):
|
|
|
+ if len(name.split('#', 1)) > 1:
|
|
|
+ _, row = name.split('#', 1)
|
|
|
+ else:
|
|
|
+ row = 0
|
|
|
+ return lambda x: x[0][1] == int(row)
|
|
|
+
|
|
|
+def unzip(_iter: List[Tuple[AutoCompleteEdit, Edit]]) -> Tuple[
|
|
|
+ List[AutoCompleteEdit], List[Edit]
|
|
|
+]:
|
|
|
+ return zip(*_iter)
|
|
|
+
|
|
|
+def extract_values(x: Union[List[AutoCompleteEdit], List[Edit]]) -> Iterable[str]:
|
|
|
+ if isinstance(x, list) or isinstance(x, tuple):
|
|
|
+ if len(x) == 0:
|
|
|
+ return []
|
|
|
+ return ( v.get_edit_text() for v in x )
|
|
|
+ raise Exception(f"Unsupported type: {type(x)}")
|
|
|
+
|
|
|
+def to_named_value(name: str) -> Callable[[str], Tuple[str,str]]:
|
|
|
+ return lambda e: (f'{name}#{e[0]}', e[1])
|
|
|
+
|
|
|
+def blank_tags_row(idx: int) -> Tuple[AutoCompleteEdit, Edit]:
|
|
|
+ return (
|
|
|
+ AutoCompleteEdit(('bg', f'tags#{idx}')), Edit(('bg', f'descriptions#{idx}')),
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
class TransactionEditor(FocusWidget):
|
|
|
|
|
|
def keypress(self, size, key):
|
|
@@ -51,6 +98,8 @@ class TransactionEditor(FocusWidget):
|
|
|
elif key == 'shift tab':
|
|
|
self.advance_focus(reverse=True)
|
|
|
elif key == 'ctrl delete':
|
|
|
+ print(self.data)
|
|
|
+ input()
|
|
|
self.clear()
|
|
|
self.focus_on(self.edit_fields['product'])
|
|
|
elif key == 'insert':
|
|
@@ -62,12 +111,24 @@ class TransactionEditor(FocusWidget):
|
|
|
|
|
|
def apply_choice(self, name, value):
|
|
|
self.apply_changes(name, value)
|
|
|
- for k,v in self.data.items():
|
|
|
- if k == name or v:
|
|
|
+ data = dict(#filter(
|
|
|
+ # in_same_row(name),
|
|
|
+ map(to_numbered_field, self.data.items())
|
|
|
+ )#)
|
|
|
+ for k,v in data.items():
|
|
|
+ if f'{k[0]}#{k[1]}' == name or v:
|
|
|
continue
|
|
|
- options = self.query_manager.unique_suggestions(k, **self.data)
|
|
|
+
|
|
|
+ _data = dict(filter(
|
|
|
+ lambda x: x[0] not in ('tags','description'),
|
|
|
+ map(lambda x: (x[0][0], x[1]), data.items())
|
|
|
+ ))
|
|
|
+ print(_data)
|
|
|
+ input()
|
|
|
+ options = self.query_manager.unique_suggestions(k[0], **_data)
|
|
|
+
|
|
|
if len(options) == 1 and k != 'ts':
|
|
|
- self.apply_changes(k, list(options)[0])
|
|
|
+ self.apply_changes(f'{k[0]}#{k[1]}', list(options)[0])
|
|
|
|
|
|
def apply_changes(self, name, value):
|
|
|
self.data = {
|
|
@@ -80,7 +141,13 @@ class TransactionEditor(FocusWidget):
|
|
|
|
|
|
@property
|
|
|
def data(self):
|
|
|
+ zipped = zip(
|
|
|
+ ['tags',],
|
|
|
+ #['tags', 'descriptions'],
|
|
|
+ map(extract_values, unzip(self._tags)),
|
|
|
+ )
|
|
|
ret = dict(itertools.chain(
|
|
|
+ *[ map(to_named_value(n), enumerate(l)) for n,l in zipped ],
|
|
|
[(k, v.get_edit_text()) for k,v in self.edit_fields.items()],
|
|
|
[(k, v.state) for k,v in self.checkboxes.items()]
|
|
|
))
|
|
@@ -93,8 +160,62 @@ class TransactionEditor(FocusWidget):
|
|
|
self.edit_fields[k].set_edit_text(v)
|
|
|
if k in self.checkboxes and v != self.checkboxes[k].state:
|
|
|
self.checkboxes[k].set_state(v)
|
|
|
+ if len(k.split('#')) > 1:
|
|
|
+ name, idx = k.split('#', 1)
|
|
|
+ if name not in ['tags', 'descriptions']:
|
|
|
+ continue
|
|
|
+ w = self._tags[int(idx)][ next(( pos for pos, n in zip(
|
|
|
+ [0, 1],
|
|
|
+ ['tags', 'descriptions']
|
|
|
+ ) if n == name ))]
|
|
|
+ w.set_edit_text(v)
|
|
|
+
|
|
|
+
|
|
|
+ def init_tags(self):
|
|
|
+ _tags = LineBox(Pile([tag[0] for tag in self._tags]),
|
|
|
+ #AutoCompletePopUp(
|
|
|
+ # tag[0],
|
|
|
+ # self.apply_choice,
|
|
|
+ # lambda: self.activity_manager.show(self.update())
|
|
|
+ #), 'streak') for tag in self._tags]),
|
|
|
+ title='Tags',
|
|
|
+ title_align='left'
|
|
|
+ )
|
|
|
+ gutter = Pile([
|
|
|
+ *[ Divider() for _ in itertools.product(
|
|
|
+ range(1), self._tags[:-1]
|
|
|
+ )],
|
|
|
+ Divider(),
|
|
|
+ Divider(),
|
|
|
+ self.buttons['add'],
|
|
|
+ ])
|
|
|
+ return _tags, gutter
|
|
|
+
|
|
|
+
|
|
|
+ def add_tag(self):
|
|
|
+ self._tags.append(
|
|
|
+ blank_tags_row(len(self._tags))
|
|
|
+ )
|
|
|
+ _tags, gutter = self.init_tags()
|
|
|
+ self.components['tags'].original_widget.contents = list(_tags.original_widget.contents)
|
|
|
+ self.components['gutter'][1].contents = list(gutter.contents)
|
|
|
+ for idx, widget in enumerate(self._tags):
|
|
|
+ connect_signal(widget[0], 'postchange', lambda w,_: self.update())
|
|
|
+ continue
|
|
|
+ connect_signal(widget[0], 'apply', lambda w, name: self.autocomplete_callback(
|
|
|
+ w, name, self.autocomplete_options(name, dict(map(
|
|
|
+ to_unnumbered_field,
|
|
|
+ #filter(
|
|
|
+ # in_same_row(name),
|
|
|
+ map(to_numbered_field, self.data.items()
|
|
|
+ #)
|
|
|
+ ))))
|
|
|
+ ))
|
|
|
+
|
|
|
|
|
|
def clear(self):
|
|
|
+ self._tags = []
|
|
|
+ self.add_tag()
|
|
|
for (k, ef) in self.edit_fields.items():
|
|
|
if k in ('ts', 'store',):
|
|
|
continue
|
|
@@ -107,7 +228,7 @@ class TransactionEditor(FocusWidget):
|
|
|
self.graph.set_data([],0)
|
|
|
return self.update()
|
|
|
|
|
|
- def update(self):
|
|
|
+ def update(self, w=None):
|
|
|
data = self.data
|
|
|
date, store = data['ts'], data['store']
|
|
|
try:
|
|
@@ -223,11 +344,19 @@ class TransactionEditor(FocusWidget):
|
|
|
activity_manager: ActivityManager,
|
|
|
query_manager: QueryManager,
|
|
|
):
|
|
|
+ self.autocomplete_options = lambda name, data: self.query_manager.unique_suggestions(name.split('#', 1)[0], **data)
|
|
|
self.activity_manager = activity_manager
|
|
|
self.query_manager = query_manager
|
|
|
self.buttons = {
|
|
|
'done': Button(('streak', u'Done')),
|
|
|
'clear': Button(('streak', u'Clear')),
|
|
|
+ 'add': Button(('streak', 'Add')),
|
|
|
+ }
|
|
|
+ self._tags = []
|
|
|
+ _tags, gutter = self.init_tags()
|
|
|
+ self.components = {
|
|
|
+ 'tags': _tags,
|
|
|
+ 'gutter': gutter,
|
|
|
}
|
|
|
self.edit_fields = {
|
|
|
'ts': AutoCompleteEdit(('bg', 'ts')),
|
|
@@ -295,7 +424,13 @@ class TransactionEditor(FocusWidget):
|
|
|
for (k, ef) in self.edit_fields.items():
|
|
|
connect_signal(ef, 'postchange', lambda *_: self.update())
|
|
|
connect_signal(ef, 'apply', lambda w, name: self.autocomplete_callback(
|
|
|
- w, name, query_manager.unique_suggestions(name, **self.data)
|
|
|
+ w, name, self.autocomplete_options(name, dict(map(
|
|
|
+ to_unnumbered_field,
|
|
|
+ #filter(
|
|
|
+ # in_same_row(name),
|
|
|
+ map(to_numbered_field, self.data.items()
|
|
|
+ #)
|
|
|
+ ))))
|
|
|
))
|
|
|
|
|
|
_widgets.update(dict([
|
|
@@ -310,7 +445,7 @@ class TransactionEditor(FocusWidget):
|
|
|
header = Text(u'Fill Transaction', 'center')
|
|
|
_copyright = Text(COPYRIGHT, 'center')
|
|
|
|
|
|
- components = {
|
|
|
+ self.components.update({
|
|
|
'bottom_button_bar': Columns(
|
|
|
[(8, self.buttons['done']), Divider(), (9, self.buttons['clear'])]
|
|
|
),
|
|
@@ -318,16 +453,20 @@ class TransactionEditor(FocusWidget):
|
|
|
lambda x: _widgets[x] if x is not None else Divider,
|
|
|
badge
|
|
|
)),
|
|
|
- }
|
|
|
- components.update({
|
|
|
+ })
|
|
|
+ self.components.update({
|
|
|
'bottom_pane': Columns([
|
|
|
Pile(map(
|
|
|
lambda x: _widgets[x] if x is not None else Divider(),
|
|
|
bottom_pane
|
|
|
)),
|
|
|
+ #(17, Columns([
|
|
|
+ #(10,self.components['tags']),
|
|
|
+ #(7,self.components['gutter']),
|
|
|
+ #])),
|
|
|
(self.graph.total_width+2, Pile([
|
|
|
LineBox(
|
|
|
- AttrMap(components['badge'], 'badge'),
|
|
|
+ AttrMap(self.components['badge'], 'badge'),
|
|
|
title="Current Price", title_align='left',
|
|
|
),
|
|
|
LineBox(
|
|
@@ -339,6 +478,7 @@ class TransactionEditor(FocusWidget):
|
|
|
})
|
|
|
connect_signal(self.buttons['done'], 'click', lambda _: self.save_and_clear_callback())
|
|
|
connect_signal(self.buttons['clear'], 'click', lambda _: self.clear())
|
|
|
+ connect_signal(self.buttons['add'], 'click', lambda _: self.add_tag())
|
|
|
|
|
|
banner = Pile([
|
|
|
Padding(header, 'center', width=('relative', 100)),
|
|
@@ -356,10 +496,10 @@ class TransactionEditor(FocusWidget):
|
|
|
], dividechars=2), title='Product', title_align='left')
|
|
|
})
|
|
|
|
|
|
- components['side_pane'] = (12, Pile([
|
|
|
+ self.components['side_pane'] = (12, Pile([
|
|
|
_widgets[r] if r is not None else Divider() for r in side_pane
|
|
|
]))
|
|
|
- components['main_pane'] = []
|
|
|
+ self.components['main_pane'] = []
|
|
|
for _, r in enumerate(layout):
|
|
|
col = []
|
|
|
for c in r:
|
|
@@ -369,24 +509,28 @@ class TransactionEditor(FocusWidget):
|
|
|
col.append(_widgets[c])
|
|
|
else:
|
|
|
col.append(Divider())
|
|
|
- components['main_pane'].append(Columns(col))
|
|
|
+ self.components['main_pane'].append(Columns(col))
|
|
|
|
|
|
- components['main_pane'] = Pile(components['main_pane'])
|
|
|
+ self.components['main_pane'] = Pile(self.components['main_pane'])
|
|
|
|
|
|
widget = Pile([
|
|
|
banner,
|
|
|
Divider(),
|
|
|
Columns([
|
|
|
- components['main_pane'], components['side_pane']
|
|
|
+ self.components['main_pane'],
|
|
|
+ self.components['side_pane'],
|
|
|
+ (12,self.components['tags']),
|
|
|
+ (8,self.components['gutter']),
|
|
|
], dividechars=2),
|
|
|
- components['bottom_pane'],
|
|
|
+ self.components['bottom_pane'],
|
|
|
Divider(),
|
|
|
- components['bottom_button_bar']
|
|
|
+ self.components['bottom_button_bar']
|
|
|
])
|
|
|
widget = Filler(widget, 'top')
|
|
|
widget = AttrMap(widget, 'bg')
|
|
|
super().__init__(widget, map(
|
|
|
lambda x: next(w[1] for w in chain(
|
|
|
+ [('tags', self._tags[-1][0]),],
|
|
|
self.buttons.items(),
|
|
|
self.edit_fields.items(),
|
|
|
self.checkboxes.items()
|
|
@@ -394,8 +538,9 @@ class TransactionEditor(FocusWidget):
|
|
|
[
|
|
|
'product', 'organic',
|
|
|
'unit', 'quantity', 'price',
|
|
|
- 'description',
|
|
|
+ 'description', 'tags',# 'add',
|
|
|
'done', 'clear',
|
|
|
'category', 'group',
|
|
|
'ts', 'store'
|
|
|
]))
|
|
|
+ self.update()
|