From 3cc4ea5b4b7d8609e1563745d22a25715c458a62 Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Sun, 12 Mar 2023 17:52:17 +0100 Subject: [PATCH] Teacher editor --- TimetableDesigner.Core/Teacher.cs | 14 ++ TimetableDesigner.Core/TimetableDay.cs | 3 +- .../{TimetableSlot.cs => TimetableSpan.cs} | 18 +- .../TimetableSpanCollection.cs | 75 ++++++++ ...ollision.cs => TimetableSpansCollision.cs} | 2 +- TimetableDesigner.Core/TimetableTemplate.cs | 40 +--- .../Collections/ObservableDictionary.cs | 153 +++++++++++++++ .../Collections/ObservableKeyValuePair.cs | 75 ++++++++ .../TimetableDesigner.Customs.csproj | 9 + TimetableDesigner.Tests/TimetableTest.cs | 54 +++--- TimetableDesigner.sln | 12 +- .../Converters/ViewModelToViewConverter.cs | 1 + .../Properties/Resources.Designer.cs | 81 ++++++++ TimetableDesigner/Properties/Resources.resx | 27 +++ TimetableDesigner/Resources/Images.xaml | 3 + .../Resources/Images/RightArrow.png | Bin 0 -> 223 bytes .../Resources/Images/Teacher.png | Bin 0 -> 1500 bytes .../Resources/Images/TeacherAdd.png | Bin 0 -> 3744 bytes TimetableDesigner/TimetableDesigner.csproj | 13 ++ .../ViewModels/ClassroomEditTabViewModel.cs | 2 +- TimetableDesigner/ViewModels/MainViewModel.cs | 49 ++++- .../ViewModels/Models/ProjectViewModel.cs | 26 +++ .../ViewModels/Models/TeacherViewModel.cs | 42 +++++ .../Models/TimetableTemplateViewModel.cs | 6 +- .../ViewModels/ProjectSettingsTabViewModel.cs | 8 +- .../ViewModels/TeacherEditTabViewModel.cs | 177 +++++++++++++++++- TimetableDesigner/Views/MainWindow.xaml | 35 +++- .../Views/TeacherEditTabView.xaml | 115 +++++++++++- 28 files changed, 947 insertions(+), 93 deletions(-) rename TimetableDesigner.Core/{TimetableSlot.cs => TimetableSpan.cs} (73%) create mode 100644 TimetableDesigner.Core/TimetableSpanCollection.cs rename TimetableDesigner.Core/{TimetableSlotsCollision.cs => TimetableSpansCollision.cs} (87%) create mode 100644 TimetableDesigner.Customs/Collections/ObservableDictionary.cs create mode 100644 TimetableDesigner.Customs/Collections/ObservableKeyValuePair.cs create mode 100644 TimetableDesigner.Customs/TimetableDesigner.Customs.csproj create mode 100644 TimetableDesigner/Resources/Images/RightArrow.png create mode 100644 TimetableDesigner/Resources/Images/Teacher.png create mode 100644 TimetableDesigner/Resources/Images/TeacherAdd.png diff --git a/TimetableDesigner.Core/Teacher.cs b/TimetableDesigner.Core/Teacher.cs index fb0cacb..479f677 100644 --- a/TimetableDesigner.Core/Teacher.cs +++ b/TimetableDesigner.Core/Teacher.cs @@ -12,6 +12,20 @@ namespace TimetableDesigner.Core public string Name { get; set; } public string Description { get; set; } + public IDictionary AvailabilityHours { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + public Teacher() + { + Name = string.Empty; + Description = string.Empty; + AvailabilityHours = new Dictionary(); + } #endregion } diff --git a/TimetableDesigner.Core/TimetableDay.cs b/TimetableDesigner.Core/TimetableDay.cs index a3d19a9..ff7bfb8 100644 --- a/TimetableDesigner.Core/TimetableDay.cs +++ b/TimetableDesigner.Core/TimetableDay.cs @@ -6,7 +6,8 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - public struct TimetableDay + [Serializable] + public class TimetableDay { #region PROPERTIES diff --git a/TimetableDesigner.Core/TimetableSlot.cs b/TimetableDesigner.Core/TimetableSpan.cs similarity index 73% rename from TimetableDesigner.Core/TimetableSlot.cs rename to TimetableDesigner.Core/TimetableSpan.cs index 8bac068..cbacd6f 100644 --- a/TimetableDesigner.Core/TimetableSlot.cs +++ b/TimetableDesigner.Core/TimetableSpan.cs @@ -3,7 +3,7 @@ namespace TimetableDesigner.Core { [Serializable] - public struct TimetableSlot + public class TimetableSpan { #region PROPERTIES @@ -16,7 +16,7 @@ namespace TimetableDesigner.Core #region CONSTRUCTORS - public TimetableSlot(TimeOnly from, TimeOnly to) + public TimetableSpan(TimeOnly from, TimeOnly to) { if (to <= from) { @@ -33,29 +33,29 @@ namespace TimetableDesigner.Core #region PUBLIC METHODS - internal TimetableSlotsCollision CheckCollision(TimetableSlot slot) + internal TimetableSpansCollision CheckCollision(TimetableSpan slot) { if (slot.To <= this.From) { - return TimetableSlotsCollision.CheckedSlotBefore; + return TimetableSpansCollision.CheckedSlotBefore; } else if (this.To <= slot.From) { - return TimetableSlotsCollision.CheckedSlotAfter; + return TimetableSpansCollision.CheckedSlotAfter; } else { if (this.From < slot.From && slot.To < this.To) { - return TimetableSlotsCollision.CheckedSlotIn; + return TimetableSpansCollision.CheckedSlotIn; } else if (this.From < slot.From && slot.From < this.To && this.To < slot.To) { - return TimetableSlotsCollision.CheckedSlotFromIn; + return TimetableSpansCollision.CheckedSlotFromIn; } else if (slot.From < this.From && this.From < slot.To && slot.To < this.To) { - return TimetableSlotsCollision.CheckedSlotToIn; + return TimetableSpansCollision.CheckedSlotToIn; } else { @@ -64,7 +64,7 @@ namespace TimetableDesigner.Core } } - public override bool Equals(object? obj) => obj is TimetableSlot slot && From == slot.From && To == slot.To; + public override bool Equals(object? obj) => obj is TimetableSpan slot && From == slot.From && To == slot.To; public override int GetHashCode() => HashCode.Combine(From, To); diff --git a/TimetableDesigner.Core/TimetableSpanCollection.cs b/TimetableDesigner.Core/TimetableSpanCollection.cs new file mode 100644 index 0000000..34f0dc4 --- /dev/null +++ b/TimetableDesigner.Core/TimetableSpanCollection.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Core +{ + public class TimetableSpanCollection : ICollection + { + #region FIELDS + + private IList _list; + + #endregion + + + + #region PROPERTIES + + public int Count => _list.Count; + public bool IsReadOnly => _list.IsReadOnly; + + #endregion + + + + #region CONSTRUCTORS + + public TimetableSpanCollection() + { + _list = new List(); + } + + #endregion + + + + #region PUBLIC METHODS + + public void Add(TimetableSpan item) + { + int i = 0; + if (Count > 0) + { + bool done = false; + while (i < Count && !done) + { + switch (item.CheckCollision(_list.ElementAt(i))) + { + case TimetableSpansCollision.CheckedSlotBefore: i++; break; + case TimetableSpansCollision.CheckedSlotAfter: done ^= true; break; + default: throw new ArgumentException("Slot collide with another slot"); + } + } + } + _list.Insert(i, item); + } + + public void Clear() => _list.Clear(); + + public bool Contains(TimetableSpan item) => _list.Contains(item); + + public void CopyTo(TimetableSpan[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + + public bool Remove(TimetableSpan item) => _list.Remove(item); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_list).GetEnumerator(); + + #endregion + } +} diff --git a/TimetableDesigner.Core/TimetableSlotsCollision.cs b/TimetableDesigner.Core/TimetableSpansCollision.cs similarity index 87% rename from TimetableDesigner.Core/TimetableSlotsCollision.cs rename to TimetableDesigner.Core/TimetableSpansCollision.cs index c698a1e..6ef758b 100644 --- a/TimetableDesigner.Core/TimetableSlotsCollision.cs +++ b/TimetableDesigner.Core/TimetableSpansCollision.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace TimetableDesigner.Core { - internal enum TimetableSlotsCollision + internal enum TimetableSpansCollision { CheckedSlotBefore, CheckedSlotAfter, diff --git a/TimetableDesigner.Core/TimetableTemplate.cs b/TimetableDesigner.Core/TimetableTemplate.cs index a242915..98c2626 100644 --- a/TimetableDesigner.Core/TimetableTemplate.cs +++ b/TimetableDesigner.Core/TimetableTemplate.cs @@ -12,7 +12,7 @@ namespace TimetableDesigner.Core #region FIELDS private List _days; - private List _slots; + private TimetableSpanCollection _slots; #endregion @@ -21,7 +21,7 @@ namespace TimetableDesigner.Core #region PROPERTIES public IEnumerable Days => _days; - public IEnumerable Slots => _slots; + public IEnumerable Slots => _slots; #endregion @@ -32,7 +32,7 @@ namespace TimetableDesigner.Core public TimetableTemplate() { _days = new List(); - _slots = new List(); + _slots = new TimetableSpanCollection(); } #endregion @@ -41,39 +41,13 @@ namespace TimetableDesigner.Core #region PUBLIC METHODS - public void AddDay(TimetableDay name) - { - _days.Add(name); - } + public void AddDay(TimetableDay name) => _days.Add(name); - public bool RemoveDay(TimetableDay day) - { - return _days.Remove(day); - } + public bool RemoveDay(TimetableDay day) => _days.Remove(day); - public void AddSlot(TimetableSlot slot) - { - int i = 0; - if (_slots.Count > 0) - { - bool done = false; - while (i < _slots.Count && !done) - { - switch (slot.CheckCollision(_slots[i])) - { - case TimetableSlotsCollision.CheckedSlotBefore: i++; break; - case TimetableSlotsCollision.CheckedSlotAfter: done ^= true; break; - default: throw new ArgumentException("Slot collide with another slot"); - } - } - } - _slots.Insert(i, slot); - } + public void AddSlot(TimetableSpan slot) => _slots.Add(slot); - public bool RemoveSlot(TimetableSlot slot) - { - return _slots.Remove(slot); - } + public bool RemoveSlot(TimetableSpan slot) => _slots.Remove(slot); #endregion } diff --git a/TimetableDesigner.Customs/Collections/ObservableDictionary.cs b/TimetableDesigner.Customs/Collections/ObservableDictionary.cs new file mode 100644 index 0000000..08de624 --- /dev/null +++ b/TimetableDesigner.Customs/Collections/ObservableDictionary.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Customs.Collections +{ + public class ObservableDictionary : ObservableCollection>, IDictionary + { + #region PROPERTIES + + public ICollection Keys => Items.Select(p => p.Key).ToList(); + public ICollection Values => Items.Select(p => p.Value).ToList(); + public bool IsReadOnly => false; + + #endregion + + + + #region INDEXERS + + public TValue this[TKey key] + { + get + { + if (!TryGetValue(key, out TValue result)) + { + throw new ArgumentException("Key not found"); + } + return result; + } + set + { + if (ContainsKey(key)) + { + GetKeyValuePairByTheKey(key).Value = value; + } + else + { + Add(key, value); + } + } + } + + #endregion + + + + #region CONSTRUCTORS + + public ObservableDictionary() : base() + { } + + public ObservableDictionary(IDictionary dictionary) : base() + { + foreach (KeyValuePair pair in dictionary) + { + this.Add(pair); + } + } + + #endregion + + + + #region PUBLIC METHODS + + public void Add(TKey key, TValue value) + { + if (ContainsKey(key)) + { + throw new ArgumentException("The dictionary already contains the key"); + } + Add(new ObservableKeyValuePair(key, value)); + } + + public void Add(KeyValuePair item) => Add(item.Key, item.Value); + + public bool Contains(KeyValuePair item) + { + ObservableKeyValuePair pair = GetKeyValuePairByTheKey(item.Key); + if (Equals(pair, default(ObservableKeyValuePair))) + { + return false; + } + return Equals(pair.Value, item.Value); + } + + public bool ContainsKey(TKey key) + { + ObservableKeyValuePair pair = ((ObservableCollection>)this).FirstOrDefault((i) => Equals(key, i.Key)); + + return !Equals(default(ObservableKeyValuePair), pair); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + public bool Remove(TKey key) + { + List> remove = ((ObservableCollection>)this).Where(pair => Equals(key, pair.Key)).ToList(); + foreach (ObservableKeyValuePair pair in remove) + { + Remove(pair); + } + return remove.Count > 0; + } + + public bool Remove(KeyValuePair item) + { + ObservableKeyValuePair pair = GetKeyValuePairByTheKey(item.Key); + if (Equals(pair, default(ObservableKeyValuePair))) + { + return false; + } + if (!Equals(pair.Value, item.Value)) + { + return false; + } + return Remove(pair); + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + value = default; + var pair = GetKeyValuePairByTheKey(key); + if (Equals(pair, default(ObservableKeyValuePair))) + { + return false; + } + value = pair.Value; + return true; + } + + IEnumerator> IEnumerable>.GetEnumerator() => ((ObservableCollection>)this).Select(i => new KeyValuePair(i.Key, i.Value)).GetEnumerator(); + + #endregion + + + + #region PRIVATE METHODS + + private ObservableKeyValuePair GetKeyValuePairByTheKey(TKey key) => ((ObservableCollection>)this).FirstOrDefault(i => i.Key.Equals(key)); + + #endregion + } +} diff --git a/TimetableDesigner.Customs/Collections/ObservableKeyValuePair.cs b/TimetableDesigner.Customs/Collections/ObservableKeyValuePair.cs new file mode 100644 index 0000000..b8703f6 --- /dev/null +++ b/TimetableDesigner.Customs/Collections/ObservableKeyValuePair.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TimetableDesigner.Customs.Collections +{ + public class ObservableKeyValuePair : INotifyPropertyChanged + { + #region FIELDS + + private TKey _key; + private TValue _value; + + #endregion + + + + #region PROPERTIES + + public TKey Key + { + get => _key; + set + { + _key = value; + NotifyPropertyChanged(nameof(Key)); + } + } + public TValue Value + { + get => _value; + set + { + _value = value; + NotifyPropertyChanged(nameof(Value)); + } + } + + #endregion + + + + #region CONSTRUCTORS + + public ObservableKeyValuePair() : this(default, default) + { } + + public ObservableKeyValuePair(TKey key, TValue value) + { + _key = key; + _value = value; + } + + #endregion + + + + #region PRIVATE METHODS + + private void NotifyPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); + + #endregion + + + + #region EVENTS + + public event PropertyChangedEventHandler? PropertyChanged; + + #endregion + } +} diff --git a/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj b/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj new file mode 100644 index 0000000..cfadb03 --- /dev/null +++ b/TimetableDesigner.Customs/TimetableDesigner.Customs.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/TimetableDesigner.Tests/TimetableTest.cs b/TimetableDesigner.Tests/TimetableTest.cs index dc97190..b691ee1 100644 --- a/TimetableDesigner.Tests/TimetableTest.cs +++ b/TimetableDesigner.Tests/TimetableTest.cs @@ -10,14 +10,14 @@ namespace TimetableDesigner.Tests [TestMethod] public void CreateValidSlotTest() { - TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)); + TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void CreateInvalidSlotTest() { - TimetableSlot slot = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(8, 0)); + TimetableSpan slot = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(8, 0)); Assert.Fail(); } @@ -26,7 +26,7 @@ namespace TimetableDesigner.Tests [ExpectedException(typeof(ArgumentException))] public void CreateSlotWithZeroLengthTest() { - TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(8, 0)); + TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(8, 0)); Assert.Fail(); } @@ -58,12 +58,12 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)); + TimetableSpan slot = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); model.AddSlot(slot); Assert.AreEqual(1, model.Slots.Count()); - Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); + Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); } [TestMethod] @@ -71,15 +71,15 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 15), new TimeOnly(9, 0)); - TimetableSlot slot2 = new TimetableSlot(new TimeOnly(9, 15), new TimeOnly(10, 0)); + TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0)); + TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 15), new TimeOnly(10, 0)); model.AddSlot(slot1); model.AddSlot(slot2); Assert.AreEqual(2, model.Slots.Count()); - Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 15), new TimeOnly(9, 0)), model.Slots.ToList()[0]); - Assert.AreEqual(new TimetableSlot(new TimeOnly(9, 15), new TimeOnly(10, 0)), model.Slots.ToList()[1]); + Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 15), new TimeOnly(9, 0)), model.Slots.ToList()[0]); + Assert.AreEqual(new TimetableSpan(new TimeOnly(9, 15), new TimeOnly(10, 0)), model.Slots.ToList()[1]); } [TestMethod] @@ -87,15 +87,15 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)); - TimetableSlot slot2 = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0)); + TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); + TimetableSpan slot2 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)); model.AddSlot(slot1); model.AddSlot(slot2); Assert.AreEqual(2, model.Slots.Count()); - Assert.AreEqual(new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); - Assert.AreEqual(new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0)), model.Slots.ToList()[1]); + Assert.AreEqual(new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)), model.Slots.ToList()[0]); + Assert.AreEqual(new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)), model.Slots.ToList()[1]); } [TestMethod] @@ -104,8 +104,8 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 30)); - TimetableSlot slot2 = new TimetableSlot(new TimeOnly(8, 30), new TimeOnly(10, 0)); + TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 30)); + TimetableSpan slot2 = new TimetableSpan(new TimeOnly(8, 30), new TimeOnly(10, 0)); model.AddSlot(slot1); model.AddSlot(slot2); @@ -119,9 +119,9 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot1 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)); - TimetableSlot slot2 = new TimetableSlot(new TimeOnly(10, 0), new TimeOnly(11, 0)); - TimetableSlot slot3 = new TimetableSlot(new TimeOnly(8, 59), new TimeOnly(10, 1)); + TimetableSpan slot1 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); + TimetableSpan slot2 = new TimetableSpan(new TimeOnly(10, 0), new TimeOnly(11, 0)); + TimetableSpan slot3 = new TimetableSpan(new TimeOnly(8, 59), new TimeOnly(10, 1)); model.AddSlot(slot1); model.AddSlot(slot2); @@ -135,13 +135,13 @@ namespace TimetableDesigner.Tests { TimetableTemplate model = new TimetableTemplate(); - TimetableSlot slot1 = new TimetableSlot(new TimeOnly(12, 0), new TimeOnly(13, 0)); - TimetableSlot slot2 = new TimetableSlot(new TimeOnly(8, 0), new TimeOnly(9, 0)); - TimetableSlot slot3 = new TimetableSlot(new TimeOnly(10, 0), new TimeOnly(11, 0)); - TimetableSlot slot4 = new TimetableSlot(new TimeOnly(14, 0), new TimeOnly(15, 0)); - TimetableSlot slot5 = new TimetableSlot(new TimeOnly(13, 0), new TimeOnly(14, 0)); - TimetableSlot slot6 = new TimetableSlot(new TimeOnly(9, 0), new TimeOnly(10, 0)); - TimetableSlot slot7 = new TimetableSlot(new TimeOnly(11, 0), new TimeOnly(12, 0)); + TimetableSpan slot1 = new TimetableSpan(new TimeOnly(12, 0), new TimeOnly(13, 0)); + TimetableSpan slot2 = new TimetableSpan(new TimeOnly(8, 0), new TimeOnly(9, 0)); + TimetableSpan slot3 = new TimetableSpan(new TimeOnly(10, 0), new TimeOnly(11, 0)); + TimetableSpan slot4 = new TimetableSpan(new TimeOnly(14, 0), new TimeOnly(15, 0)); + TimetableSpan slot5 = new TimetableSpan(new TimeOnly(13, 0), new TimeOnly(14, 0)); + TimetableSpan slot6 = new TimetableSpan(new TimeOnly(9, 0), new TimeOnly(10, 0)); + TimetableSpan slot7 = new TimetableSpan(new TimeOnly(11, 0), new TimeOnly(12, 0)); model.AddSlot(slot1); model.AddSlot(slot2); @@ -151,9 +151,9 @@ namespace TimetableDesigner.Tests model.AddSlot(slot6); model.AddSlot(slot7); - List slots = model.Slots.ToList(); + List slots = model.Slots.ToList(); - TimetableSlot testSlot = slots[0]; + TimetableSpan testSlot = slots[0]; for (int i = 1; i < slots.Count; i++) { if (testSlot.To > slots[i].From) diff --git a/TimetableDesigner.sln b/TimetableDesigner.sln index ca65abf..e180e15 100644 --- a/TimetableDesigner.sln +++ b/TimetableDesigner.sln @@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.Core", "T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.Tests", "TimetableDesigner.Tests\TimetableDesigner.Tests.csproj", "{A9B4DB89-A007-472A-9C80-B6340458AC1B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.MessageBox", "TimetableDesigner.MessageBox\TimetableDesigner.MessageBox.csproj", "{95992646-6D81-4FF4-885E-8F0BE2312D54}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimetableDesigner.MessageBox", "TimetableDesigner.MessageBox\TimetableDesigner.MessageBox.csproj", "{95992646-6D81-4FF4-885E-8F0BE2312D54}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.Customs", "TimetableDesigner.Customs\TimetableDesigner.Customs.csproj", "{BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,6 +53,14 @@ Global {95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|Any CPU.Build.0 = Release|Any CPU {95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|x64.ActiveCfg = Release|Any CPU {95992646-6D81-4FF4-885E-8F0BE2312D54}.Release|x64.Build.0 = Release|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|x64.ActiveCfg = Debug|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Debug|x64.Build.0 = Debug|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|Any CPU.Build.0 = Release|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|x64.ActiveCfg = Release|Any CPU + {BCA4CD04-FD49-4C28-9743-F4F6C1888E7E}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TimetableDesigner/Converters/ViewModelToViewConverter.cs b/TimetableDesigner/Converters/ViewModelToViewConverter.cs index 880572f..ced4095 100644 --- a/TimetableDesigner/Converters/ViewModelToViewConverter.cs +++ b/TimetableDesigner/Converters/ViewModelToViewConverter.cs @@ -23,6 +23,7 @@ namespace TimetableDesigner.Converters { typeof(WelcomeTabViewModel), typeof(WelcomeTabView) }, { typeof(ProjectSettingsTabViewModel), typeof(ProjectSettingsTabView) }, { typeof(ClassroomEditTabViewModel), typeof(ClassroomEditTabView) }, + { typeof(TeacherEditTabViewModel), typeof(TeacherEditTabView) }, }; #endregion diff --git a/TimetableDesigner/Properties/Resources.Designer.cs b/TimetableDesigner/Properties/Resources.Designer.cs index a4ad81d..0e48220 100644 --- a/TimetableDesigner/Properties/Resources.Designer.cs +++ b/TimetableDesigner/Properties/Resources.Designer.cs @@ -114,6 +114,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to New teacher. + /// + public static string Global_DefaultTeacherName { + get { + return ResourceManager.GetString("Global.DefaultTeacherName", resourceCulture); + } + } + /// /// Looks up a localized string similar to no project loaded. /// @@ -321,6 +330,24 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Teachers. + /// + public static string Main_Treeview_Teachers { + get { + return ResourceManager.GetString("Main.Treeview.Teachers", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit teacher. + /// + public static string Main_Treeview_Teachers_ContextMenu_Edit { + get { + return ResourceManager.GetString("Main.Treeview.Teachers.ContextMenu.Edit", resourceCulture); + } + } + /// /// Looks up a localized string similar to Error. /// @@ -429,6 +456,15 @@ namespace TimetableDesigner.Properties { } } + /// + /// Looks up a localized string similar to Teacher editing. + /// + public static string Tabs_TeacherEdit { + get { + return ResourceManager.GetString("Tabs.TeacherEdit", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome. /// @@ -437,5 +473,50 @@ namespace TimetableDesigner.Properties { return ResourceManager.GetString("Tabs.Welcome", resourceCulture); } } + + /// + /// Looks up a localized string similar to Availability hours. + /// + public static string TeacherEdit_AvailabilityHours { + get { + return ResourceManager.GetString("TeacherEdit.AvailabilityHours", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Description. + /// + public static string TeacherEdit_Description { + get { + return ResourceManager.GetString("TeacherEdit.Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to "From" value is higher or equal to "To" value. + /// + public static string TeacherEdit_Message_FromHigherThanTo { + get { + return ResourceManager.GetString("TeacherEdit.Message.FromHigherThanTo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Hour collide with another hour. + /// + public static string TeacherEdit_Message_HourCollision { + get { + return ResourceManager.GetString("TeacherEdit.Message.HourCollision", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Name. + /// + public static string TeacherEdit_Name { + get { + return ResourceManager.GetString("TeacherEdit.Name", resourceCulture); + } + } } } diff --git a/TimetableDesigner/Properties/Resources.resx b/TimetableDesigner/Properties/Resources.resx index fba3efd..5d3e4c2 100644 --- a/TimetableDesigner/Properties/Resources.resx +++ b/TimetableDesigner/Properties/Resources.resx @@ -135,6 +135,9 @@ New project + + New teacher + no project loaded @@ -204,6 +207,12 @@ Remove + + Teachers + + + Edit teacher + Error @@ -240,7 +249,25 @@ Project settings + + Teacher editing + Welcome + + Availability hours + + + Description + + + "From" value is higher or equal to "To" value + + + Hour collide with another hour + + + Name + \ No newline at end of file diff --git a/TimetableDesigner/Resources/Images.xaml b/TimetableDesigner/Resources/Images.xaml index 89c8dbe..27aff9f 100644 --- a/TimetableDesigner/Resources/Images.xaml +++ b/TimetableDesigner/Resources/Images.xaml @@ -8,9 +8,12 @@ + + + \ No newline at end of file diff --git a/TimetableDesigner/Resources/Images/RightArrow.png b/TimetableDesigner/Resources/Images/RightArrow.png new file mode 100644 index 0000000000000000000000000000000000000000..fd248b653ad84fad40f781d5358c30de77e22dc1 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWNC^*s6 z#WAE}&fDvT+=m=^+8&z!D?jSFy4mlS{L+NGTCD7vvPV?KYvoUrIqy3?!Fqy32=YGm>L1P&U`!1iqD<9UfFM|e)>#3`T^q`Z8Lq(Nh22WQ%mvv4FO#s?uOUD2J literal 0 HcmV?d00001 diff --git a/TimetableDesigner/Resources/Images/Teacher.png b/TimetableDesigner/Resources/Images/Teacher.png new file mode 100644 index 0000000000000000000000000000000000000000..70d2cd97770d0144487038cc2f9e2b77b81d8776 GIT binary patch literal 1500 zcmV<21ta>2P)hQQn z2TP9@XU?W$rBRC@V;1k#y4&8v(Z_h*86L49pne?!>Nx(j8Iu=4k48CsU5i)Wr{mQP z4aRSzVf?#P{B<=2i)%FqYRKfdTG!hnAzpVLj9B3RxsFq-6INjSM%r;2`ejcFUS3ba zi|z!BU5m%bQ)&br(oKZRv|gi3tNX*@z$`g<;T!vFPE+AhxEe&XMkkW7tXrQQ0o4ml-wRj5+ z{k$t0qn9Hwa%mNwbuP!fbIYJ8Po2{ChTq^FcraptUv(PG_p?;Us{|By-nHf{HuSfv zFw(gK&n_;*(+f-RJ!jK~b%7x>`T0G|t4EnD%BQ_24tmh_H$%b$&gkg z3i}#eP!W&z_N5p)vjBsq=cBcC5#BA0gJ0!Z=Wx}o!#Qk^C!owOoUFo`v^>dCslWn$ zBtTg*#>cOnwZQ!Wb}nq9NWH3%}ysxYGFv zUUaKr>5RmdnoVHx*UV6XBj$cr0i#Y1v0Oc8OPFp#TCg2Hx#^g@zXrk55Na}yg_+DCl!pgjOPHx2O4ESDndZwbYk?ETbzpW!&rE^f#sN^F zO=dw9<#mF7@5jz)0cBst9kxJWEF020cQd4V1AOwfgV`CyecO;{(-@p_S3q9b2TF0EUl6tc{m10v zK|)W{$Ap<`0ZCaODBr3(f;bfT4|(ww@GiLxN>hrSAnLDZMR-NF)jn&6at) z;1(!xNv|NYs=!;hH$izBdIgzP1w7*!&v-`kf6hN-$cDVImTS-e0000A{0v$g&3vxPn~+td(L^^&*y)8p6~bf{oS7D*=NTb9jw>Ms>nhhkae~; z1ZVKAy?o0^flrdSC>a8gxJ-3*wa zR61U5@@9h?j7r9kD|1`B}cBlVHmYR0lK9E ziAABp!orYYx=03#g3>WEGD2yiQD`&*WFXiPbdFCrg3eZ7miVGU0N8#kDw9KH&|%A( zKE8|)4qiIaL4e>U8$k^ua=)mJyT=QXBQ3bReAi5A+ro@LvqU^8LL@xDWI1$VB4*rqgJDN`uX@3MNC4t_#+d=0gGY0dxu{0I#NJ_+J2tO8)QpWuc&u?@$!)z@r40MbP3=nAK7a6c=SkRX0A3^ZpfGo5&S{I?M zi_k&4>gZzi(O5l0xV8>fTYE)$g$?cnvJc1S|M8bsGz`T4 z@R!k-VGwm?eZycYUB>$OEms|{#$++bA$|aP#W^VV1!Xh*Ibl95z>ESm7_VmL?@tB$ z69IEvUb!$Gq>eVy;LjFe0RX7>&$*$#MuuA6aest{L;WRq+zR18wkT-#We(i>;2DDY zc8Gw3Z^sZo2hScBc*yv=bv%MV#1g1rTmI4x2xK=egx3yl_JOu~8|3_2YG`P<_ju;9 zj^W|UExVyLTFt=X(+$@i)=tjB5u}m2!n-Y%tK!5+BV`J_lW@v#;%#GZciR2@b3)Or z{JCjS^a8FxI;f-UK$N^}zX-OeGGAupn=!tHA_gk#=&9kS;Iz6h1| ziRA}+@+yW2itsH6&Aq7BOhLt-k;t-a!%R_cN)Pg2-m9NhZ)|+1;V1D3&@6EkLGk^=kKwN`lJ}<)kL91@mmHq`%5uYp_-U`uPPi<4d7m9-xqIgHs2&oRMdbS-|R~* zv^k~l)7|*_1i~i$vzB@q3wKQSWSDZbY-Cl4U3%~DI3HRqcjvC`A*5B^OcT9!re^L& zu5dD$?LV5!ujaTD8YVJxZ{UP61)|Rd<55ydfYZ0@(Ls+K)~w5E6HTG2B<7i`-H`iq zRG)N<>zHh@fhGIqQ}+T$-JHn5?N*7D**I`4OWv$Kee9y!@F+{GpK- zXyf_g@g*fhmFp7qJ>5MO)hVeZ1kJ3*!DrC9az)bIQAobY0pZfd*1lONa=axrdg;8L z*Yo+O?PG%M&e%~di$y<~mVdn*z#KJ`7OTG%JYUSc%`KctZU2yVBAeSjp?l9m6<0f$ z>wtJBnF`^ztPTAoNLp-q#*`1?XRIaLSXJuQR9p;h-Ld_BM@?77$$dF#stpLKb20AL zykbm}!p&*<49Tr3XI@B3qdC>gNGt*E*dLR1KGu5G&Yu1GCmU_F_uP zXAkY|iuzplY4t+;6)A@YMAncWCb?#3v1D;Z;?rtpW)fLWEL+k>tX;J|X}dt6^jcx^ zf~a{rf!vK2mc5A86ubER2O|n*W+8+uMvzD+4J9Z4V*Y46#k4T%-T(v{Juy0$*GSQ| zkWWv$8%R9w zb*|!uL+Xc`8Hv1Do4i#;u?atHKv48aGQA_o-xD7|E|G4=t6+ZTOT2pQ`u9 zLi#ubiz1kDzV$9gmZ~xm%JIku0&{(9kL4kT-za@!*X-V*^ zY7s}y4sIBDoGFcBrz0LKJD!E+8d#Nzuiq!d-KWW46z%w}*?sCF;99QvgB{HEQqh=f zpe}xIHT1^|nuWu-6P;DDc7lbrm(_;tYh1i|**kL^BUHV*HJ`g^fLEqqJx7i?`c~>m zhl5!D)=SmG0K^-mSh~@O{@hUCo-&m)Fw-oR2g*k$B137{b7M&Rr=K3kjE-O`c3EhK z-?nalA1hnGGo`br!{AuSkC%@B?1?%p;7z`Ldi5obm~>x3{z<8{^oR5(y+7nTFC2f! zM~~>cx7Dqg3IysF>EtnM)l$kUhxOgfloMr_*iRlCcC8aakFqeHBdSezy2LNR4tvhY z)t24(+1|d{t>_;4@X&=GqsdLO6QxTu@4W^(zXs<^Hi=sbbGt(hHyLXCY+INg@!-9G z9i@Ez@Ya@{TknjUN$oFid#({#+`Y>17I#0@HqLd1mcM~J3r7tt!5J?u9>}(A&6c@U z*mWub9kcyP)#p3oo;EN?0~vxG&#g^ACWtb8^Fix_ot(HlkB8%I3mp4(QM7P-b;k7l z>{P?J{Hr|k6Q;UY;*^s{AwJ2!MoZ2pp8GF9_4@@FcX>2>!uIvQ{1mcjyUOux{^#?T zbV&{DME;-#8J{cUO(Hq*wS)o82WtuFo58Jh`#Ner| zrftuT6uIvIeC+81ua{3?zvnzNoMQLxlJkB%uBZj98G;B@R^6ED!DpbZ)Lh3!N@(xe zqnlgi;8Yz+Xfn3a`)SigL-_rLu5px3$h>y!l5d%TxntBCmg-A9GjE9VNFQ&KF+TFv z9#bXiT(@ofZJqC?FwZL&M4#R@;G6e*i4?6%2iEK_?vyVS3J4OTc}mn_b9sO3%L_wO z0UcS9SC6Ffhh!FG^v~wBZ^+&lW)MfFM09A#nu{TF)aa*P7i66Z>TMs=6! zT1sLh^s^D6nf+k;vzxPoS$TTFJKnJu>`dJI_B`Cj*ORAR7SjclLhFLtdEOI?>Kkr< z-1@Q$S|Q8FI8VVkhOmBDWO@?Q2A>rhxZfFndqlB#l?eF~l|7RrY~nIgqi#F3#fcq( z@L@l$8W3Oo;M2Tlr146%ScC5(Ddz4h8c(Y-_qDkewxjS-lKmy?hwYC(O-N4P0vGNmSNq&@6HTdOk8Yo@=YJZs&N$Au5IjHe4feATMYCp@SbB77ooGrHO|hVu~Rv`tJa;(O`mG2 zLgon120r7&*Nr`|xf0Le7YAyTXOGGA%S~UOq&jq--&biLxsi(L46Jq!Yp>}OHEe_^ z&yQ{P7~E}JW~OqoD_M);f57%!M!F3al9N+UnUh{j4stTq5N&|&YKA}DQ!TUn$I{l) Kfly(7IPu>~16Wf4 literal 0 HcmV?d00001 diff --git a/TimetableDesigner/TimetableDesigner.csproj b/TimetableDesigner/TimetableDesigner.csproj index 736ee59..ced6d28 100644 --- a/TimetableDesigner/TimetableDesigner.csproj +++ b/TimetableDesigner/TimetableDesigner.csproj @@ -20,8 +20,11 @@ + + + @@ -52,12 +55,21 @@ Always + + Always + Always Always + + Always + + + Always + @@ -67,6 +79,7 @@ + diff --git a/TimetableDesigner/ViewModels/ClassroomEditTabViewModel.cs b/TimetableDesigner/ViewModels/ClassroomEditTabViewModel.cs index 6e15a52..0a8f315 100644 --- a/TimetableDesigner/ViewModels/ClassroomEditTabViewModel.cs +++ b/TimetableDesigner/ViewModels/ClassroomEditTabViewModel.cs @@ -42,7 +42,7 @@ namespace TimetableDesigner.ViewModels public ClassroomEditTabViewModel() : this(new ClassroomViewModel(new Core.Classroom())) { } - public ClassroomEditTabViewModel(ClassroomViewModel classroom) + public ClassroomEditTabViewModel(ClassroomViewModel classroom) : base() { _classroom = classroom; } diff --git a/TimetableDesigner/ViewModels/MainViewModel.cs b/TimetableDesigner/ViewModels/MainViewModel.cs index 70c3c6b..172feb8 100644 --- a/TimetableDesigner/ViewModels/MainViewModel.cs +++ b/TimetableDesigner/ViewModels/MainViewModel.cs @@ -45,6 +45,9 @@ namespace TimetableDesigner.ViewModels public ICommand NewClassroomCommand { get; set; } public ICommand EditClassroomCommand { get; set; } public ICommand RemoveClassroomCommand { get; set; } + public ICommand NewTeacherCommand { get; set; } + public ICommand EditTeacherCommand { get; set; } + public ICommand RemoveTeacherCommand { get; set; } public ObservableCollection Tabs { @@ -95,6 +98,9 @@ namespace TimetableDesigner.ViewModels NewClassroomCommand = new RelayCommand(param => NewClassroom()); EditClassroomCommand = new RelayCommand(EditClassroom); RemoveClassroomCommand = new RelayCommand(DeleteClassroom); + NewTeacherCommand = new RelayCommand(param => NewTeacher()); + EditTeacherCommand = new RelayCommand(EditTeacher); + RemoveTeacherCommand = new RelayCommand(DeleteTeacher); _tabs = new ObservableCollection { @@ -189,10 +195,8 @@ namespace TimetableDesigner.ViewModels private void EditClassroom(ClassroomViewModel classroomViewModel) { - Debug.WriteLine("textedit"); - ClassroomEditTabViewModel classroomEdit = new ClassroomEditTabViewModel() + ClassroomEditTabViewModel classroomEdit = new ClassroomEditTabViewModel(classroomViewModel) { - Classroom = classroomViewModel, TabTitle = $"{Resources.Tabs_ClassroomEdit}: {classroomViewModel.Name}", IsTabClosable = true }; @@ -202,13 +206,50 @@ namespace TimetableDesigner.ViewModels private void DeleteClassroom(ClassroomViewModel classroomViewModel) { - Debug.WriteLine("textdelete"); if (Project is not null) { Project.Classrooms.Remove(classroomViewModel); } } + private void NewTeacher() + { + if (Project is not null) + { + Teacher teacher = new Teacher() + { + Name = Resources.Global_DefaultTeacherName + }; + TeacherViewModel teacherVM = new TeacherViewModel(teacher); + Project.Teachers.Add(teacherVM); + EditTeacher(teacherVM); + } + } + + private void EditTeacher(TeacherViewModel teacherViewModel) + { + if (Project is not null) + { + TeacherEditTabViewModel teacherEdit = new TeacherEditTabViewModel(teacherViewModel, Project.TimetableTemplate) + { + Teacher = teacherViewModel, + TabTitle = $"{Resources.Tabs_TeacherEdit}: {teacherViewModel.Name}", + IsTabClosable = true + }; + Tabs.Add(teacherEdit); + SelectedTab = teacherEdit; + } + + } + + private void DeleteTeacher(TeacherViewModel teacherViewModel) + { + if (Project is not null) + { + Project.Teachers.Remove(teacherViewModel); + } + } + #endregion } } diff --git a/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs b/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs index b4a5b2d..01f853b 100644 --- a/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/ProjectViewModel.cs @@ -61,6 +61,7 @@ namespace TimetableDesigner.ViewModels.Models } public TimetableTemplateViewModel TimetableTemplate { get; set; } public ObservableCollection Classrooms { get; set; } + public ObservableCollection Teachers { get; set; } #endregion @@ -76,6 +77,9 @@ namespace TimetableDesigner.ViewModels.Models Classrooms = new ObservableCollection(); Classrooms.CollectionChanged += Classrooms_CollectionChanged; + + Teachers = new ObservableCollection(); + Teachers.CollectionChanged += Teachers_CollectionChanged; } #endregion @@ -106,6 +110,28 @@ namespace TimetableDesigner.ViewModels.Models } } + private void Teachers_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + IList? added = e.NewItems as IList; + IList? removed = e.OldItems as IList; + + if (removed is not null) + { + foreach (TeacherViewModel vm in removed) + { + _project.Teachers.Remove(vm.Teacher); + } + } + + if (added is not null) + { + foreach (TeacherViewModel vm in added) + { + _project.Teachers.Add(vm.Teacher); + } + } + } + #endregion } } diff --git a/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs b/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs index 679ed7f..b899218 100644 --- a/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/TeacherViewModel.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using TimetableDesigner.Core; +using TimetableDesigner.Customs.Collections; using TimetableDesigner.ViewModels.Base; namespace TimetableDesigner.ViewModels.Models @@ -46,6 +48,7 @@ namespace TimetableDesigner.ViewModels.Models } } } + public ObservableDictionary> AvailabilityHours => new ObservableDictionary>(Teacher.AvailabilityHours.ToDictionary(i => i.Key, i => new ObservableCollection(i.Value))); #endregion @@ -59,5 +62,44 @@ namespace TimetableDesigner.ViewModels.Models } #endregion + + + + #region PUBLIC METHODS + + public void AddDay(TimetableDay day) + { + if (day is not null) + { + Teacher.AvailabilityHours.Add(day, new TimetableSpanCollection()); + NotifyPropertyChanged(nameof(AvailabilityHours)); + } + } + + public void RemoveDay(TimetableDay day) + { + Teacher.AvailabilityHours.Remove(day); + NotifyPropertyChanged(nameof(AvailabilityHours)); + } + + public void AddHours(TimetableDay day, TimetableSpan hours) + { + if (day is not null && hours is not null) + { + Teacher.AvailabilityHours[day].Add(hours); + NotifyPropertyChanged(nameof(AvailabilityHours)); + } + } + + public void RemoveHours(TimetableDay day, TimetableSpan hours) + { + if (day is not null) + { + Teacher.AvailabilityHours[day].Remove(hours); + NotifyPropertyChanged(nameof(AvailabilityHours)); + } + } + + #endregion } } diff --git a/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs b/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs index d434c83..f2be1a8 100644 --- a/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs +++ b/TimetableDesigner/ViewModels/Models/TimetableTemplateViewModel.cs @@ -24,7 +24,7 @@ namespace TimetableDesigner.ViewModels.Models public TimetableTemplate TimetableTemplate => _timetableTemplate; public ObservableCollection Days => new ObservableCollection(_timetableTemplate.Days); - public ObservableCollection Slots => new ObservableCollection(_timetableTemplate.Slots); + public ObservableCollection Slots => new ObservableCollection(_timetableTemplate.Slots); #endregion @@ -55,13 +55,13 @@ namespace TimetableDesigner.ViewModels.Models NotifyPropertyChanged(nameof(Days)); } - public void AddSlot(TimetableSlot slot) + public void AddSlot(TimetableSpan slot) { _timetableTemplate.AddSlot(slot); NotifyPropertyChanged(nameof(Slots)); } - public void RemoveSlot(TimetableSlot slot) + public void RemoveSlot(TimetableSpan slot) { _timetableTemplate.RemoveSlot(slot); NotifyPropertyChanged(nameof(Slots)); diff --git a/TimetableDesigner/ViewModels/ProjectSettingsTabViewModel.cs b/TimetableDesigner/ViewModels/ProjectSettingsTabViewModel.cs index 9de6f09..2c3d1fd 100644 --- a/TimetableDesigner/ViewModels/ProjectSettingsTabViewModel.cs +++ b/TimetableDesigner/ViewModels/ProjectSettingsTabViewModel.cs @@ -73,14 +73,14 @@ namespace TimetableDesigner.ViewModels #region CONSTRUCTORS - public ProjectSettingsTabViewModel() + public ProjectSettingsTabViewModel() : base() { Project = new ProjectViewModel(new Project()); AddDayCommand = new RelayCommand(param => AddDay()); AddSlotCommand = new RelayCommand(param => AddSlot()); RemoveDayCommand = new RelayCommand(RemoveDay); - RemoveSlotCommand = new RelayCommand(RemoveSlot); + RemoveSlotCommand = new RelayCommand(RemoveSlot); _newDayName = string.Empty; _newSlotFrom = new DateTime(1, 1, 1, 8, 0, 0); @@ -118,7 +118,7 @@ namespace TimetableDesigner.ViewModels try { - Project.TimetableTemplate.AddSlot(new TimetableSlot(from, to)); + Project.TimetableTemplate.AddSlot(new TimetableSpan(from, to)); double delta = (to - from).TotalMinutes; DateTime newFrom = NewSlotTo.Value; @@ -133,7 +133,7 @@ namespace TimetableDesigner.ViewModels } } - private void RemoveSlot(TimetableSlot slot) => Project.TimetableTemplate.RemoveSlot(slot); + private void RemoveSlot(TimetableSpan slot) => Project.TimetableTemplate.RemoveSlot(slot); #endregion } diff --git a/TimetableDesigner/ViewModels/TeacherEditTabViewModel.cs b/TimetableDesigner/ViewModels/TeacherEditTabViewModel.cs index eadf0dd..b99e56b 100644 --- a/TimetableDesigner/ViewModels/TeacherEditTabViewModel.cs +++ b/TimetableDesigner/ViewModels/TeacherEditTabViewModel.cs @@ -1,13 +1,188 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; +using TimetableDesigner.Commands; +using TimetableDesigner.Core; +using TimetableDesigner.MessageBox; +using TimetableDesigner.Properties; using TimetableDesigner.ViewModels.Base; +using TimetableDesigner.ViewModels.Models; namespace TimetableDesigner.ViewModels { - public class TeacherEditTabViewModel : BaseViewModel + public class TeacherEditTabViewModel : BaseTabViewModel { + #region FIELDS + + private TeacherViewModel _teacher; + private TimetableTemplateViewModel _timetableTemplate; + + private TimetableDay _selectedDay; + + private TimetableDay _selectedNewDay; + private DateTime? _newHourFrom; + private DateTime? _newHourTo; + + #endregion + + + + #region PROPERTIES + + public TeacherViewModel Teacher + { + get => _teacher; + set + { + if (value != _teacher) + { + _teacher = value; + NotifyPropertyChanged(nameof(Teacher)); + } + } + } + public TimetableTemplateViewModel TimetableTemplate + { + get => _timetableTemplate; + set + { + if (value != _timetableTemplate) + { + _timetableTemplate = value; + NotifyPropertyChanged(nameof(TimetableTemplate)); + } + } + } + + public ICommand AddDayCommand { get; set; } + public ICommand RemoveDayCommand { get; set; } + public ICommand AddHourCommand { get; set; } + public ICommand RemoveHourCommand { get; set; } + + public TimetableDay SelectedDay + { + get => _selectedDay; + set + { + if (_selectedDay != value) + { + _selectedDay = value; + NotifyPropertyChanged(nameof(SelectedDay)); + NotifyPropertyChanged(nameof(SelectedDayHours)); + } + } + } + + public TimetableDay SelectedNewDay + { + get => _selectedNewDay; + set + { + if (_selectedNewDay != value) + { + _selectedNewDay = value; + NotifyPropertyChanged(nameof(SelectedNewDay)); + } + } + } + + public DateTime? NewHourFrom + { + get => _newHourFrom; + set + { + _newHourFrom = value; + NotifyPropertyChanged(nameof(NewHourFrom)); + } + } + public DateTime? NewHourTo + { + get => _newHourTo; + set + { + _newHourTo = value; + NotifyPropertyChanged(nameof(NewHourTo)); + } + } + + public ObservableCollection SelectedDayHours => SelectedDay is not null ? Teacher.AvailabilityHours[SelectedDay] : new ObservableCollection(); + + #endregion + + + + #region CONSTRUCTORS + + public TeacherEditTabViewModel() : this(new TeacherViewModel(new Core.Teacher()), new TimetableTemplateViewModel(new Core.TimetableTemplate())) + { } + + public TeacherEditTabViewModel(TeacherViewModel teacher, TimetableTemplateViewModel timetableTemplate) : base() + { + _teacher = teacher; + _timetableTemplate = timetableTemplate; + + AddDayCommand = new RelayCommand(param => AddDay()); + RemoveDayCommand = new RelayCommand(RemoveDay); + AddHourCommand = new RelayCommand(param => AddHour()); + RemoveHourCommand = new RelayCommand(RemoveHour); + + _newHourFrom = new DateTime(1, 1, 1, 8, 0, 0); + _newHourTo = new DateTime(1, 1, 1, 16, 0, 0); + } + + #endregion + + + + #region PRIVATE METHODS + + private void AddDay() + { + if (!Teacher.AvailabilityHours.ContainsKey(SelectedNewDay)) + { + Teacher.AddDay(SelectedNewDay); + } + } + + private void RemoveDay(TimetableDay day) + { + Teacher.RemoveDay(day); + } + + private void AddHour() + { + if (NewHourFrom.HasValue && NewHourTo.HasValue) + { + TimeOnly from = new TimeOnly(NewHourFrom.Value.Hour, NewHourFrom.Value.Minute); + TimeOnly to = new TimeOnly(NewHourTo.Value.Hour, NewHourTo.Value.Minute); + if (from >= to) + { + MessageBoxService.ShowError(Resources.TeacherEdit_Message_FromHigherThanTo); + return; + } + + try + { + Teacher.AddHours(SelectedDay, new TimetableSpan(from, to)); + } + catch (ArgumentException) + { + MessageBoxService.ShowError(Resources.TeacherEdit_Message_HourCollision); + } + } + NotifyPropertyChanged(nameof(SelectedDayHours)); + } + + private void RemoveHour(TimetableSpan hour) + { + Teacher.RemoveHours(SelectedDay, hour); + NotifyPropertyChanged(nameof(SelectedDayHours)); + } + + #endregion } } diff --git a/TimetableDesigner/Views/MainWindow.xaml b/TimetableDesigner/Views/MainWindow.xaml index 9e9613d..af9f8ff 100644 --- a/TimetableDesigner/Views/MainWindow.xaml +++ b/TimetableDesigner/Views/MainWindow.xaml @@ -46,7 +46,7 @@ - + @@ -76,9 +76,7 @@ - + @@ -96,6 +94,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TimetableDesigner/Views/TeacherEditTabView.xaml b/TimetableDesigner/Views/TeacherEditTabView.xaml index e29592f..64fa92c 100644 --- a/TimetableDesigner/Views/TeacherEditTabView.xaml +++ b/TimetableDesigner/Views/TeacherEditTabView.xaml @@ -3,10 +3,117 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:TimetableDesigner.Views" + xmlns:p = "clr-namespace:TimetableDesigner.Properties" + xmlns:vm="clr-namespace:TimetableDesigner.ViewModels" + xmlns:local="clr-namespace:TimetableDesigner.Views" xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - - - + + + + + + + + + + + + + + + + + + +