Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refator day timetracking #158

Merged
merged 27 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions app/controllers/internal_api/v1/timesheet_entry_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
class InternalApi::V1::TimesheetEntryController < InternalApi::V1::ApplicationController
include Timesheet

before_action :check_bill_status, only: [:create, :update]

def index
timesheet_entries = current_user.timesheet_entries.during(params[:from], params[:to])
entries = formatted_entries_by_date(timesheet_entries)
render json: { success: true, entries: entries }
end

def create
timesheet_entry = project.timesheet_entries.new(timesheet_entry_params)
timesheet_entry = current_project.timesheet_entries.new(timesheet_entry_params)
timesheet_entry.user = current_user
if timesheet_entry.save
render json: { success: true, entry: timesheet_entry.formatted_entry }
Expand All @@ -20,32 +22,38 @@ def create
end

def update
timesheet_entry.project = project
if timesheet_entry.update(timesheet_entry_params)
render json: { success: true, entry: timesheet_entry.formatted_entry }
current_timesheet_entry.project = current_project
if current_timesheet_entry.update(timesheet_entry_params)
render json: { success: true, entry: current_timesheet_entry.formatted_entry }
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
else
render json: timesheet_entry.errors, status: :unprocessable_entity
end
end

def destroy
if timesheet_entry.destroy
if current_timesheet_entry.destroy
render json: { success: true }
else
render json: timesheet_entry.errors, status: :unprocessable_entity
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
end
end

private
def project
project ||= Project.find_by!(name: params[:project_name])
def current_project
@_current_project ||= current_company.projects.find(params[:project_id])
end

def timesheet_entry
timesheet_entry ||= TimesheetEntry.find(params[:id])
def current_timesheet_entry
@_current_timesheet_entry ||= current_user.timesheet_entries.find(params[:id])
end

def timesheet_entry_params
params.require(:timesheet_entry).permit(:project_id, :duration, :work_date, :note)
params.require(:timesheet_entry).permit(:project_id, :duration, :work_date, :note, :bill_status)
end

def check_bill_status
if timesheet_entry_params[:bill_status] == "billed"
render json: { success: false, error: "You cannot bill an entry that has already been billed" }, status: :unprocessable_entity
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
14 changes: 8 additions & 6 deletions app/controllers/time_tracking_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ class TimeTrackingController < ApplicationController
skip_after_action :verify_authorized

def index
@is_admin = current_user.is_admin?
@clients = current_company.clients.includes(:projects)
@projects = {}
@clients.map do |c|
@projects[c.name] = c.projects
is_admin = current_user.is_admin?
clients = current_company.clients.includes(:projects)
projects = {}
clients.map do |c|
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
projects[c.name] = c.projects
end

timesheet_entries = current_user.timesheet_entries.during(
Date.today.beginning_of_week,
Date.today.end_of_week
)
@entries = formatted_entries_by_date(timesheet_entries)
entries = formatted_entries_by_date(timesheet_entries)

render :index, locals: { is_admin: is_admin, clients: clients, projects: projects, entries: entries }
end
end
Binary file added app/javascript/images/check-box-unchecked.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
114 changes: 78 additions & 36 deletions app/javascript/src/components/timesheet-entry/AddEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getNumberWithOrdinal } from "../../helpers/ordinal";

interface props {
setNewEntryView: React.Dispatch<React.SetStateAction<boolean>>;
clients: client[];
clients: Iclient[];
projects: object;
selectedDateInfo: object;
setEntryList: React.Dispatch<React.SetStateAction<object[]>>;
Expand All @@ -15,7 +15,7 @@ interface props {
setEditEntryId: React.Dispatch<React.SetStateAction<number>>;
}

interface client {
interface Iclient {
name: string;
}

Expand All @@ -36,21 +36,32 @@ const AddEntry: React.FC<props> = ({
const [duration, setDuration] = useState("00:00");
const [client, setClient] = useState("");
const [project, setProject] = useState("");
const [projectId, setProjectId] = useState(0);
const [billable, setBillable] = useState(false);

useEffect(() => {
if (editEntryId) {
const entry = entryList[selectedFullDate].filter(
entry => entry.id === editEntryId
)[0];
if (entry) {
setNote(entry.note);
setDuration(minutesToHHMM(entry.duration));
setClient(entry.client);
setProject(entry.project);
}
if (!editEntryId) return;
const entry = entryList[selectedFullDate].filter(
entry => entry.id === editEntryId
)[0];

if (entry) {
setNote(entry.note);
setDuration(minutesToHHMM(entry.duration));
setClient(entry.client);
setProject(entry.project);
setProjectId(entry.project_id);
if (entry.bill_status == "unbilled" || entry.bill_status == "billed")
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
setBillable(true);
}
}, []);

useEffect(() => {
if (!project) return;
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
const id = projects[client].filter(p => p.name === project)[0].id;
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
setProjectId(Number(id));
}, [project]);

const handleDurationChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = e.target.value;
let [hh, mm] = v.split(":");
Expand All @@ -62,15 +73,15 @@ const AddEntry: React.FC<props> = ({

const handleSave = async () => {
if (!note && !project) return;
const entry = {
work_date: selectedFullDate,
duration: minutesFromHHMM(duration),
note: note
};

const res = await timesheetEntryApi.create({
project_name: project,
timesheet_entry: entry
project_id: projectId,
timesheet_entry: {
work_date: selectedFullDate,
duration: minutesFromHHMM(duration),
note: note,
bill_status: billable ? "unbilled" : "non_billable"
}
});

if (res.data?.success) {
Expand All @@ -93,8 +104,9 @@ const AddEntry: React.FC<props> = ({

const handleEdit = async () => {
const res = await timesheetEntryApi.update(editEntryId, {
project_name: project,
project_id: projectId,
timesheet_entry: {
bill_status: billable ? "unbilled" : "non_billable",
note: note,
duration: minutesFromHHMM(duration),
work_date: selectedFullDate
Expand Down Expand Up @@ -129,6 +141,7 @@ const AddEntry: React.FC<props> = ({
setClient(e.target.value);
setProject(projects[e.target.value][0].name);
}}
value={client || "Client"}
name="client"
id="client"
className="w-60 bg-miru-gray-100 rounded-sm mt-2 h-8"
Expand All @@ -144,7 +157,10 @@ const AddEntry: React.FC<props> = ({
</select>

<select
onChange={e => setProject(e.target.value)}
onChange={e => {
setProject(e.target.value);
}}
value={project}
name="project"
id="project"
className="w-60 bg-miru-gray-100 rounded-sm mt-2 h-8"
Expand All @@ -156,7 +172,9 @@ const AddEntry: React.FC<props> = ({
)}
{client &&
projects[client].map((p, i) => (
abinashpa marked this conversation as resolved.
Show resolved Hide resolved
<option key={i.toString()}>{p.name}</option>
<option data-project-id={p.id} key={i.toString()}>
{p.name}
</option>
))}
</select>
</span>
Expand All @@ -168,28 +186,52 @@ const AddEntry: React.FC<props> = ({
cols={60}
name="notes"
placeholder=" Notes"
className="w-60 h-18 rounded-sm bg-miru-gray-100 my-2"
className="p-1 w-60 h-18 rounded-sm bg-miru-gray-100 my-2"
></textarea>
</div>
<div className="w-60">
<div className="p-1 w-60 bg-miru-gray-100 rounded-sm mt-2 h-8">
{`${getNumberWithOrdinal(selectedDateInfo["date"])} ${
selectedDateInfo["month"]
}, ${selectedDateInfo["year"]}`}
<div className="flex justify-between">
<div className="p-1 h-8 w-29 bg-miru-gray-100 rounded-sm mt-2 mr-1 text-sm flex justify-center items-center">
{`${getNumberWithOrdinal(selectedDateInfo["date"])} ${
selectedDateInfo["month"]
}, ${selectedDateInfo["year"]}`}
</div>
<input
value={duration}
onChange={handleDurationChange}
type="text"
className="p-1 h-8 w-29 bg-miru-gray-100 rounded-sm mt-2 ml-1 text-sm"
/>
</div>
<div className="flex items-center">
{billable ? (
<img
onClick={() => {
setBillable(false);
}}
className="inline"
src="/checkbox-checked.svg"
alt="checkbox"
/>
) : (
<img
onClick={() => {
setBillable(true);
}}
className="inline"
src="/checkbox-unchecked.svg"
alt="checkbox"
/>
)}
<h4>Billable</h4>
</div>
<input
value={duration}
onChange={handleDurationChange}
type="text"
className="w-60 bg-miru-gray-100 rounded-sm mt-2 h-8"
/>
</div>
<div className="mr-4 my-2 ml-14">
{editEntryId === 0 ? (
<button
onClick={handleSave}
className={
"mb-1 h-8 w-38 text-xs py-1 px-6 rounded border text-white font-bold " +
"mb-1 h-8 w-38 text-xs py-1 px-6 rounded border text-white font-bold tracking-widest " +
(note && client && project
? "bg-miru-han-purple-1000 hover:border-transparent"
: "bg-miru-gray-1000")
Expand All @@ -201,7 +243,7 @@ const AddEntry: React.FC<props> = ({
<button
onClick={() => handleEdit()}
className={
"mb-1 h-8 w-38 text-xs py-1 px-6 rounded border text-white font-bold " +
"mb-1 h-8 w-38 text-xs py-1 px-6 rounded border text-white font-bold tracking-widest " +
(note && client && project
? "bg-miru-han-purple-1000 hover:border-transparent"
: "bg-miru-gray-1000")
Expand All @@ -215,7 +257,7 @@ const AddEntry: React.FC<props> = ({
setNewEntryView(false);
setEditEntryId(0);
}}
className="mt-1 h-8 w-38 text-xs py-1 px-6 rounded border border-miru-han-purple-1000 bg-transparent hover:bg-miru-han-purple-1000 text-miru-han-purple-600 font-bold hover:text-white hover:border-transparent"
className="mt-1 h-8 w-38 text-xs py-1 px-6 rounded border border-miru-han-purple-1000 bg-transparent hover:bg-miru-han-purple-1000 text-miru-han-purple-600 font-bold hover:text-white hover:border-transparent tracking-widest"
>
CANCEL
</button>
Expand Down
15 changes: 12 additions & 3 deletions app/javascript/src/components/timesheet-entry/EntryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface props {
duration: number;
handleDeleteEntry: (id: number) => void;
setEditEntryId: React.Dispatch<React.SetStateAction<number>>;
bill_status: string;
}

const EntryCard: React.FC<props> = ({
Expand All @@ -18,7 +19,8 @@ const EntryCard: React.FC<props> = ({
note,
duration,
handleDeleteEntry,
setEditEntryId
setEditEntryId,
bill_status
}) => (
<div className="flex justify-between items-center shadow-2xl w-full p-6 mt-10 rounded-lg">
<div className="">
Expand All @@ -27,9 +29,16 @@ const EntryCard: React.FC<props> = ({
<p className="text-lg mx-2">•</p>
<p className="text-lg">{project}</p>
</div>
<p className="text-sm text-miru-dark-purple-400">{note}</p>
<p className="text-sm text-miru-dark-purple-400 max-w-xs break-words whitespace-pre-wrap">
{note}
</p>
</div>
<div className="flex">
<div className="flex items-center">
{bill_status !== "non_billable" && (
<p className="mr-6 text-xs w-20 h-4 flex justify-center font-semibold tracking-widest bg-miru-han-purple-200 rounded-lg text-miru-han-purple-1000">
BILLABLE
</p>
)}
<p className="text-4xl">{minutesToHHMM(duration)}</p>
<button onClick={() => setEditEntryId(id)} className="mx-10">
<img
Expand Down
18 changes: 8 additions & 10 deletions app/javascript/src/components/timesheet-entry/TimeTracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,14 @@ const TimeTracking: React.FC<props> = ({
<div>
<div className="mb-6">
<div className="flex justify-between items-center bg-miru-han-purple-1000 h-10 w-full">
<button className="items-center text-white tracking-widest border-2 rounded-lg h-6 w-20 text-base ml-4">
<button
onClick={() => {
setWeekDay(0);
setSelectDate(dayjs().weekday());
}}
className="text-center"
>
TODAY
</button>
<button
onClick={() => {
setWeekDay(0);
setSelectDate(dayjs().weekday());
}}
className="flex items-center justify-center text-white tracking-widest border-2 rounded-lg h-6 w-20 text-base ml-4"
>
TODAY
</button>
<div className="flex">
<button
Expand Down
5 changes: 3 additions & 2 deletions app/models/timesheet_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def formatted_entry
{
id: id,
project: project.name,
project_id: project_id,
client: project.client.name,
duration: duration,
note: note,
Expand All @@ -49,9 +50,9 @@ def formatted_entry

private
def insure_bill_status_is_set
return if project.nil?
return if bill_status.present? || project.nil?
abinashpa marked this conversation as resolved.
Show resolved Hide resolved

if project.billable?
if project.billable
self.bill_status = :unbilled
else
self.bill_status = :non_billable
Expand Down
2 changes: 1 addition & 1 deletion app/views/time_tracking/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div class="" id="root-page">
<%= react_component("timesheet-entry/TimeTracking", clients: @clients, projects: @projects, entries: @entries, isAdmin: @is_admin, prerender: false) %>
<%= react_component("timesheet-entry/TimeTracking", clients: clients, projects: projects, entries: entries, isAdmin: is_admin, prerender: false) %>
</div>
Loading