tudla_hubstaff 0.1.0

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.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +270 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/stylesheets/tudla_hubstaff/application.css +15 -0
  6. data/app/assets/tailwind/tudla_hubstaff/engine.css +32 -0
  7. data/app/components/tudla_hubstaff/activity_row_component.html.erb +46 -0
  8. data/app/components/tudla_hubstaff/activity_row_component.rb +67 -0
  9. data/app/components/tudla_hubstaff/base_component.rb +10 -0
  10. data/app/components/tudla_hubstaff/project_row_component.html.erb +22 -0
  11. data/app/components/tudla_hubstaff/project_row_component.rb +21 -0
  12. data/app/components/tudla_hubstaff/task_activities_component.html.erb +82 -0
  13. data/app/components/tudla_hubstaff/task_activities_component.rb +93 -0
  14. data/app/components/tudla_hubstaff/task_row_component.html.erb +22 -0
  15. data/app/components/tudla_hubstaff/task_row_component.rb +17 -0
  16. data/app/components/tudla_hubstaff/ui/map_button_component.html.erb +8 -0
  17. data/app/components/tudla_hubstaff/ui/map_button_component.rb +21 -0
  18. data/app/components/tudla_hubstaff/ui/mapping_modal_component.html.erb +51 -0
  19. data/app/components/tudla_hubstaff/ui/mapping_modal_component.rb +34 -0
  20. data/app/components/tudla_hubstaff/ui/modal_component.html.erb +36 -0
  21. data/app/components/tudla_hubstaff/ui/modal_component.rb +17 -0
  22. data/app/components/tudla_hubstaff/ui/pagination_component.html.erb +19 -0
  23. data/app/components/tudla_hubstaff/ui/pagination_component.rb +47 -0
  24. data/app/components/tudla_hubstaff/ui/status_badge_component.html.erb +3 -0
  25. data/app/components/tudla_hubstaff/ui/status_badge_component.rb +26 -0
  26. data/app/components/tudla_hubstaff/ui/table_component.html.erb +20 -0
  27. data/app/components/tudla_hubstaff/ui/table_component.rb +15 -0
  28. data/app/components/tudla_hubstaff/ui.rb +4 -0
  29. data/app/components/tudla_hubstaff/user_activities_list_component.html.erb +70 -0
  30. data/app/components/tudla_hubstaff/user_activities_list_component.rb +92 -0
  31. data/app/components/tudla_hubstaff/user_row_component.html.erb +22 -0
  32. data/app/components/tudla_hubstaff/user_row_component.rb +13 -0
  33. data/app/controllers/tudla_hubstaff/activities_controller.rb +88 -0
  34. data/app/controllers/tudla_hubstaff/application_controller.rb +14 -0
  35. data/app/controllers/tudla_hubstaff/projects_controller.rb +75 -0
  36. data/app/controllers/tudla_hubstaff/tasks_controller.rb +75 -0
  37. data/app/controllers/tudla_hubstaff/users_controller.rb +75 -0
  38. data/app/helpers/tudla_hubstaff/application_helper.rb +4 -0
  39. data/app/javascript/tudla_hubstaff/application.js +21 -0
  40. data/app/javascript/tudla_hubstaff/controllers/activity_task_mapping_modal_controller.js +183 -0
  41. data/app/javascript/tudla_hubstaff/controllers/index.js +15 -0
  42. data/app/javascript/tudla_hubstaff/controllers/project_mapping_modal_controller.js +180 -0
  43. data/app/javascript/tudla_hubstaff/controllers/task_mapping_modal_controller.js +181 -0
  44. data/app/javascript/tudla_hubstaff/controllers/user_mapping_modal_controller.js +181 -0
  45. data/app/jobs/tudla_hubstaff/application_job.rb +4 -0
  46. data/app/jobs/tudla_hubstaff/fetch_updated_activities_job.rb +40 -0
  47. data/app/mailers/tudla_hubstaff/application_mailer.rb +6 -0
  48. data/app/models/tudla_hubstaff/activity.rb +6 -0
  49. data/app/models/tudla_hubstaff/application_record.rb +5 -0
  50. data/app/models/tudla_hubstaff/config.rb +8 -0
  51. data/app/models/tudla_hubstaff/organization_update.rb +5 -0
  52. data/app/models/tudla_hubstaff/project.rb +14 -0
  53. data/app/models/tudla_hubstaff/task.rb +9 -0
  54. data/app/models/tudla_hubstaff/user.rb +16 -0
  55. data/app/services/tudla_hubstaff/api_client.rb +64 -0
  56. data/app/services/tudla_hubstaff/api_connection.rb +54 -0
  57. data/app/services/tudla_hubstaff/concerns/activity_syncing.rb +46 -0
  58. data/app/services/tudla_hubstaff/concerns/task_syncing.rb +32 -0
  59. data/app/services/tudla_hubstaff/fetch_updated_activities_service.rb +28 -0
  60. data/app/services/tudla_hubstaff/get_task_service.rb +24 -0
  61. data/app/services/tudla_hubstaff/sync_activities_service.rb +28 -0
  62. data/app/services/tudla_hubstaff/sync_projects_service.rb +51 -0
  63. data/app/services/tudla_hubstaff/sync_tasks_service.rb +31 -0
  64. data/app/services/tudla_hubstaff/sync_users_service.rb +53 -0
  65. data/app/views/layouts/tudla_hubstaff/application.html.erb +19 -0
  66. data/app/views/tudla_hubstaff/projects/unmapped.html.erb +75 -0
  67. data/app/views/tudla_hubstaff/tasks/unmapped.html.erb +75 -0
  68. data/app/views/tudla_hubstaff/users/map_tudla_user.turbo_stream.erb +1 -0
  69. data/app/views/tudla_hubstaff/users/unmapped.html.erb +75 -0
  70. data/config/importmap.rb +7 -0
  71. data/config/routes.rb +41 -0
  72. data/db/migrate/20251230220607_create_tudla_hubstaff_users.rb +16 -0
  73. data/db/migrate/20251230222524_create_tudla_hubstaff_tasks.rb +21 -0
  74. data/db/migrate/20251230223731_create_tudla_hubstaff_projects.rb +15 -0
  75. data/db/migrate/20251230225408_add_hubstaff_ids_to_models.rb +14 -0
  76. data/db/migrate/20251231041753_add_last_updated_at_to_models.rb +7 -0
  77. data/db/migrate/20251231043338_add_tudla_ids_to_models.rb +14 -0
  78. data/db/migrate/20251231045209_create_tudla_hubstaff_activities.rb +30 -0
  79. data/db/migrate/20251231074018_create_tudla_hubstaff_configs.rb +15 -0
  80. data/db/migrate/20251231074556_create_tudla_hubstaff_organization_updates.rb +12 -0
  81. data/lib/generators/tudla_hubstaff/components_generator.rb +68 -0
  82. data/lib/generators/tudla_hubstaff/views_generator.rb +54 -0
  83. data/lib/tasks/tudla_hubstaff_tasks.rake +18 -0
  84. data/lib/tudla_hubstaff/engine.rb +88 -0
  85. data/lib/tudla_hubstaff/provider.rb +12 -0
  86. data/lib/tudla_hubstaff/version.rb +3 -0
  87. data/lib/tudla_hubstaff.rb +49 -0
  88. metadata +442 -0
@@ -0,0 +1,7 @@
1
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
2
+ pin "tudla_hubstaff/application", to: "tudla_hubstaff/application.js"
3
+ pin "tudla_hubstaff/controllers/index", to: "tudla_hubstaff/controllers/index.js"
4
+ pin "tudla_hubstaff/controllers/user_mapping_modal_controller", to: "tudla_hubstaff/controllers/user_mapping_modal_controller.js"
5
+ pin "tudla_hubstaff/controllers/task_mapping_modal_controller", to: "tudla_hubstaff/controllers/task_mapping_modal_controller.js"
6
+ pin "tudla_hubstaff/controllers/project_mapping_modal_controller", to: "tudla_hubstaff/controllers/project_mapping_modal_controller.js"
7
+ pin "tudla_hubstaff/controllers/activity_task_mapping_modal_controller", to: "tudla_hubstaff/controllers/activity_task_mapping_modal_controller.js"
data/config/routes.rb ADDED
@@ -0,0 +1,41 @@
1
+ TudlaHubstaff::Engine.routes.draw do
2
+ resources :users, only: [] do
3
+ collection do
4
+ get :unmapped
5
+ get :available_tudla_users
6
+ end
7
+ member do
8
+ patch :map_tudla_user
9
+ end
10
+ end
11
+
12
+ resources :tasks, only: [] do
13
+ collection do
14
+ get :unmapped
15
+ get :available_tudla_tasks
16
+ end
17
+ member do
18
+ patch :map_tudla_task
19
+ end
20
+ end
21
+
22
+ resources :projects, only: [] do
23
+ collection do
24
+ get :unmapped
25
+ get :available_tudla_projects
26
+ end
27
+ member do
28
+ patch :map_tudla_project
29
+ end
30
+ end
31
+
32
+ resources :activities, only: [] do
33
+ collection do
34
+ get :user_activities
35
+ get :available_tudla_tasks
36
+ end
37
+ member do
38
+ patch :map_activity_task
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ class CreateTudlaHubstaffUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_users do |t|
4
+ t.string :name, null: false
5
+ t.string :first_name, null: false
6
+ t.string :last_name, null: false
7
+ t.string :email
8
+ t.string :time_zone, null: false, default: "UTC"
9
+ t.string :ip_address
10
+ t.string :status, null: false, default: "active"
11
+
12
+ t.timestamps
13
+ end
14
+ add_index :tudla_hubstaff_users, :email, unique: true
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ class CreateTudlaHubstaffTasks < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_tasks do |t|
4
+ t.integer :integration_id
5
+ t.string :status
6
+ t.integer :project_id, null: false
7
+ t.string :project_type, null: false
8
+ t.string :summary, null: false
9
+ t.text :details
10
+ t.string :remote_id
11
+ t.string :remote_alternate_id
12
+ t.integer :lock_version
13
+ t.json :metadata, null: false, default: {}
14
+ t.datetime :completed_at
15
+ t.datetime :due_at
16
+
17
+ t.timestamps
18
+ end
19
+ add_index :tudla_hubstaff_tasks, :remote_id
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTudlaHubstaffProjects < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_projects do |t|
4
+ t.string :name, null: false
5
+ t.text :description
6
+ t.string :status
7
+ t.string :project_type, null: false
8
+ t.integer :client_id
9
+ t.json :metadata, null: false, default: {}
10
+
11
+ t.timestamps
12
+ end
13
+ add_index :tudla_hubstaff_projects, :client_id
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ class AddHubstaffIdsToModels < ActiveRecord::Migration[8.1]
2
+ disable_ddl_transaction!
3
+
4
+ def change
5
+ add_column :tudla_hubstaff_users, :user_id, :bigint
6
+ add_index :tudla_hubstaff_users, :user_id, algorithm: :concurrently
7
+
8
+ add_column :tudla_hubstaff_projects, :project_id, :bigint
9
+ add_index :tudla_hubstaff_projects, :project_id, algorithm: :concurrently
10
+
11
+ add_column :tudla_hubstaff_tasks, :task_id, :bigint
12
+ add_index :tudla_hubstaff_tasks, :task_id, algorithm: :concurrently
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ class AddLastUpdatedAtToModels < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :tudla_hubstaff_users, :last_updated_at, :datetime
4
+ add_column :tudla_hubstaff_projects, :last_updated_at, :datetime
5
+ add_column :tudla_hubstaff_tasks, :last_updated_at, :datetime
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ class AddTudlaIdsToModels < ActiveRecord::Migration[8.1]
2
+ disable_ddl_transaction!
3
+
4
+ def change
5
+ add_column :tudla_hubstaff_users, :tudla_user_id, :bigint
6
+ add_index :tudla_hubstaff_users, :tudla_user_id, algorithm: :concurrently
7
+
8
+ add_column :tudla_hubstaff_projects, :tudla_project_id, :bigint
9
+ add_index :tudla_hubstaff_projects, :tudla_project_id, algorithm: :concurrently
10
+
11
+ add_column :tudla_hubstaff_tasks, :tudla_task_id, :bigint
12
+ add_index :tudla_hubstaff_tasks, :tudla_task_id, algorithm: :concurrently
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ class CreateTudlaHubstaffActivities < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_activities do |t|
4
+ t.bigint :activity_id, null: false
5
+ t.string :date, null: false
6
+ t.bigint :user_id, null: false
7
+ t.bigint :project_id
8
+ t.bigint :task_id
9
+ t.integer :keyboard, default: 0
10
+ t.integer :mouse, default: 0
11
+ t.integer :overall, default: 0
12
+ t.integer :tracked, default: 0
13
+ t.integer :input_tracked, default: 0
14
+ t.integer :manual, default: 0
15
+ t.integer :idle, default: 0
16
+ t.integer :resumed, default: 0
17
+ t.integer :billable, default: 0
18
+ t.integer :work_break, default: 0
19
+ t.datetime :last_updated_at
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ add_index :tudla_hubstaff_activities, :activity_id, unique: true
25
+ add_index :tudla_hubstaff_activities, :date
26
+ add_index :tudla_hubstaff_activities, :user_id
27
+ add_index :tudla_hubstaff_activities, :project_id
28
+ add_index :tudla_hubstaff_activities, :task_id
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ class CreateTudlaHubstaffConfigs < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_configs do |t|
4
+ t.string :tudla_organization_type
5
+ t.bigint :tudla_organization_id
6
+ t.string :personal_access_token
7
+ t.bigint :organization_id
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :tudla_hubstaff_configs, [ :tudla_organization_type, :tudla_organization_id ], name: "index_configs_on_tudla_organization"
13
+ add_index :tudla_hubstaff_configs, :organization_id
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTudlaHubstaffOrganizationUpdates < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :tudla_hubstaff_organization_updates do |t|
4
+ t.bigint :organization_id, null: false
5
+ t.datetime :last_updated_at
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :tudla_hubstaff_organization_updates, :organization_id
11
+ end
12
+ end
@@ -0,0 +1,68 @@
1
+ require "rails/generators/base"
2
+
3
+ module TudlaHubstaff
4
+ module Generators
5
+ class ComponentsGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../../app/components", __dir__)
7
+
8
+ desc "Copies TudlaHubstaff ViewComponents to the host application for customization."
9
+
10
+ argument :scope, required: false, default: nil,
11
+ desc: "The scope to copy (e.g., ui, or a specific component like ui/modal)"
12
+
13
+ class_option :force, type: :boolean, default: false,
14
+ desc: "Overwrite existing files"
15
+
16
+ class_option :templates_only, type: :boolean, default: false,
17
+ desc: "Copy only the template files (.html.erb), not the Ruby classes"
18
+
19
+ def copy_components
20
+ if scope.present?
21
+ copy_scoped_components
22
+ else
23
+ copy_all_components
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def copy_scoped_components
30
+ source_path = "tudla_hubstaff/#{scope}"
31
+ target_path = "app/components/tudla_hubstaff/#{scope}"
32
+
33
+ if options[:templates_only]
34
+ copy_templates_only(source_path, target_path)
35
+ else
36
+ if File.directory?(File.join(self.class.source_root, source_path))
37
+ directory source_path, target_path
38
+ say_status :success, "Copied #{scope} components to #{target_path}", :green
39
+ else
40
+ say_status :error, "No components found for scope '#{scope}'", :red
41
+ end
42
+ end
43
+ end
44
+
45
+ def copy_all_components
46
+ if options[:templates_only]
47
+ copy_templates_only("tudla_hubstaff", "app/components/tudla_hubstaff")
48
+ else
49
+ directory "tudla_hubstaff", "app/components/tudla_hubstaff"
50
+ say_status :success, "Copied all TudlaHubstaff components to app/components/tudla_hubstaff", :green
51
+ end
52
+ end
53
+
54
+ def copy_templates_only(source_path, target_path)
55
+ full_source = File.join(self.class.source_root, source_path)
56
+
57
+ Dir.glob(File.join(full_source, "**/*.html.erb")).each do |template_file|
58
+ relative_path = template_file.sub("#{self.class.source_root}/", "")
59
+ destination = relative_path.sub("tudla_hubstaff/", "app/components/tudla_hubstaff/")
60
+
61
+ copy_file relative_path, destination
62
+ end
63
+
64
+ say_status :success, "Copied component templates to #{target_path}", :green
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,54 @@
1
+ require "rails/generators/base"
2
+
3
+ module TudlaHubstaff
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("../../../app/views", __dir__)
7
+
8
+ desc "Copies TudlaHubstaff views to the host application for customization."
9
+
10
+ argument :scope, required: false, default: nil,
11
+ desc: "The scope to copy (e.g., users, tasks, projects, layouts)"
12
+
13
+ class_option :force, type: :boolean, default: false,
14
+ desc: "Overwrite existing files"
15
+
16
+ AVAILABLE_SCOPES = %w[users tasks projects layouts shared].freeze
17
+
18
+ def copy_views
19
+ if scope.present?
20
+ validate_scope!
21
+ copy_scoped_views
22
+ else
23
+ copy_all_views
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def validate_scope!
30
+ return if AVAILABLE_SCOPES.include?(scope)
31
+
32
+ say_status :error, "Invalid scope '#{scope}'. Available scopes: #{AVAILABLE_SCOPES.join(', ')}", :red
33
+ raise Thor::Error, "Invalid scope"
34
+ end
35
+
36
+ def copy_scoped_views
37
+ source_path = "tudla_hubstaff/#{scope}"
38
+ target_path = "app/views/tudla_hubstaff/#{scope}"
39
+
40
+ if File.directory?(File.join(self.class.source_root, source_path))
41
+ directory source_path, target_path
42
+ say_status :success, "Copied #{scope} views to #{target_path}", :green
43
+ else
44
+ say_status :error, "No views found for scope '#{scope}'", :red
45
+ end
46
+ end
47
+
48
+ def copy_all_views
49
+ directory "tudla_hubstaff", "app/views/tudla_hubstaff"
50
+ say_status :success, "Copied all TudlaHubstaff views to app/views/tudla_hubstaff", :green
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ # desc "Explaining what the task does"
2
+ # task :tudla_hubstaff do
3
+ # # Task goes here
4
+ # end
5
+ namespace :app do
6
+ namespace :tailwindcss do
7
+ desc "Watch and build Tailwind CSS for the engine"
8
+ task :watch do
9
+ require "tailwindcss-rails"
10
+ # Construct the command to watch engine files and output to builds/
11
+ cmd = "#{Tailwindcss::Commands.executable} " \
12
+ "-i #{TudlaHubstaff::Engine.root.join("app/assets/stylesheets/tudla_hubstaff/application.css")} " \
13
+ "-o #{TudlaHubstaff::Engine.root.join("app/assets/builds/tudla_hubstaff.css")} " \
14
+ "--watch"
15
+ system cmd
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,88 @@
1
+ module TudlaHubstaff
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace TudlaHubstaff
4
+
5
+ # Configure Zeitwerk to use UI instead of Ui for the ui directory
6
+ initializer "tudla_hubstaff.zeitwerk_inflections", before: :set_autoload_paths do
7
+ Rails.autoloaders.each do |autoloader|
8
+ autoloader.inflector.inflect("ui" => "UI")
9
+ end
10
+ end
11
+
12
+ config.generators do |g|
13
+ g.test_framework :rspec
14
+ g.fixture_replacement :factory_bot
15
+ g.factory_bot dir: "spec/factories"
16
+ end
17
+
18
+ # Advanced FactoryBot Configuration
19
+ # We hook into the initializer to append the path relative to the Engine's root.
20
+ initializer "tudla_hubstaff.factories", after: "factory_bot.set_factory_paths" do
21
+ if defined?(FactoryBot)
22
+ FactoryBot.definition_file_paths << File.expand_path("../../../spec/factories", __FILE__)
23
+ end
24
+ end
25
+
26
+ # Define an initializer that runs when the app boots
27
+ initializer "tudla_hubstaff.register_integration",
28
+ after: "app.load_config_initializers" do
29
+ config.to_prepare do
30
+ # Register this gem's classes
31
+ Rails.logger.info "Registering Hubstaff integration"
32
+ TudlaContracts::Integrations::Registry.register(
33
+ "hubstaff",
34
+ type: "time_sheet",
35
+ provider_class: "TudlaHubstaff::Provider",
36
+ config_class: "TudlaHubstaff::Config"
37
+ )
38
+ TudlaContracts::Integrations::Registry.register_view_for_slot(
39
+ "task_show_tab",
40
+ TudlaContracts::Integrations::ViewDefinitions::ShowTabDefinition.new(
41
+ id: :tudla_hubstaff_task_show_tab,
42
+ label: "Hubstaff Activities",
43
+ component_class: TudlaHubstaff::TaskActivitiesComponent,
44
+ priority: 10,
45
+ if: ->(task) { true }
46
+ )
47
+ )
48
+ TudlaContracts::Integrations::Registry.register_view_for_slot(
49
+ "dashboard_section",
50
+ TudlaContracts::Integrations::ViewDefinitions::DashboardSectionDefinition.new(
51
+ id: :tudla_hubstaff_dashboard_section,
52
+ label: "Hubstaff Activities",
53
+ component_class: TudlaHubstaff::UserActivitiesListComponent,
54
+ priority: 10,
55
+ if: ->(user) { true }
56
+ )
57
+ )
58
+ end
59
+ end
60
+
61
+ initializer "tudla_hubstaff.importmap", before: "importmap" do |app|
62
+ if app.config.respond_to?(:importmap)
63
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
64
+ app.config.importmap.cache_sweepers << Engine.root.join("app/javascript")
65
+ end
66
+ end
67
+
68
+ initializer "tudla_hubstaff.assets" do |app|
69
+ if app.config.respond_to?(:assets)
70
+ app.config.assets.paths << Engine.root.join("app/javascript")
71
+ end
72
+ end
73
+
74
+ # Ensure the engine's assets are visible to the host's pipeline
75
+ initializer "tudla_hubstaff.assets.precompile" do |app|
76
+ app.config.assets.paths << root.join("app/assets/tailwind")
77
+ end
78
+
79
+ initializer "tudla_hubstaff.view_component" do |app|
80
+ # ViewComponent automatically picks up components from app/components
81
+ # No additional configuration needed when using autoload_paths
82
+ end
83
+
84
+ # Configurable layout - allows host app to specify which layout to use
85
+ config.tudla_hubstaff = ActiveSupport::OrderedOptions.new
86
+ config.tudla_hubstaff.layout = "tudla_hubstaff/application"
87
+ end
88
+ end
@@ -0,0 +1,12 @@
1
+ module TudlaHubstaff
2
+ class Provider < TudlaContracts::TimeSheet::Base
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def daily_activities
8
+ # Implement the logic to fetch the time sheet from the provider
9
+ [] # NOOP for now
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module TudlaHubstaff
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,49 @@
1
+ # Requires are necessary for the Engine to load these gems' tasks and classes
2
+ require "solid_queue"
3
+ require "solid_cache"
4
+ require "solid_cable"
5
+ require "tailwindcss-rails"
6
+ require "importmap-rails"
7
+ require "faraday"
8
+ require "httpx/adapters/faraday"
9
+ require "openid_connect"
10
+ require "tudla_contracts"
11
+ require "turbo-rails"
12
+ require "view_component"
13
+ require "pagy"
14
+
15
+ require "tudla_hubstaff/version"
16
+ require "tudla_hubstaff/provider"
17
+ require "tudla_hubstaff/engine"
18
+
19
+
20
+ module TudlaHubstaff
21
+ class << self
22
+ # Configuration for the host interface class
23
+ # Host apps should set this to their own class that inherits from
24
+ # TudlaContracts::Integration::HostInterface
25
+ attr_writer :host_interface_class
26
+
27
+ def host_interface_class
28
+ @host_interface_class || raise(<<~ERROR)
29
+ TudlaHubstaff.host_interface_class is not configured.
30
+
31
+ Please configure it in an initializer:
32
+
33
+ # config/initializers/tudla_hubstaff.rb
34
+ TudlaHubstaff.host_interface_class = "::HostInterface"
35
+
36
+ Your class should inherit from TudlaContracts::Integration::HostInterface
37
+ and implement the required methods like `available_users_for_user`.
38
+ ERROR
39
+ end
40
+
41
+ def host_interface
42
+ @host_interface ||= host_interface_class.constantize.new
43
+ end
44
+
45
+ def reset_host_interface!
46
+ @host_interface = nil
47
+ end
48
+ end
49
+ end